Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15651] ファイルの概要

コミット

  • コミットハッシュ: 0406c63ea38520adf040f3f72ea3d8008dd53480
  • 作者: Rob Pike r@golang.org
  • 日付: Fri Mar 8 16:39:54 2013 -0800

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/0406c63ea38520adf040f3f72ea3d8008dd53480

元コミット内容

text/template: revert minor change to Name method
For better printing, I recently changed Name to return "<unnamed>" for templates
with empty names, but this causes trouble for the many packages that used "" as
the template name, so restore the old behavior.
It's usually printed as a quoted string anyway, so it should be fine.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/7577044

変更の背景

このコミットは、Go言語の標準ライブラリである text/template パッケージの Template 型の Name() メソッドに加えられた以前の変更を元に戻すものです。

以前の変更では、テンプレート名が空文字列 ("") の場合に、Name() メソッドが "<unnamed>" という文字列を返すように修正されました。この変更の意図は、デバッグ時やログ出力時に、名前のないテンプレートがより分かりやすく表示されるようにすることでした。例えば、fmt.Printf("%q", t.Name()) のように引用符付きで出力される場合、"" ではなく "<unnamed>" と表示されることで、それが意図的に名前が付けられていないテンプレートであることが明確になります。

しかし、この変更は、Goのエコシステム内の多くの既存パッケージに問題を引き起こしました。これらのパッケージは、テンプレート名が空文字列であること ("") を前提として動作しており、Name() メソッドが "<unnamed>" を返すようになったことで、予期せぬ動作やエラーが発生する可能性がありました。特に、テンプレート名を比較したり、空文字列であるかどうかをチェックしたりするロジックを持つパッケージが影響を受けました。

このような互換性の問題と、既存のコードベースへの影響を考慮し、以前の変更を元に戻し、Name() メソッドがテンプレート名が空の場合に再び空文字列 ("") を返すようにすることが決定されました。コミットメッセージにある「It's usually printed as a quoted string anyway, so it should be fine.」という記述は、たとえ空文字列が返されても、通常は引用符付きで出力されるため、視認性の問題は小さいという判断を示しています。

前提知識の解説

Go言語の text/template パッケージ

text/template パッケージは、Go言語でテキストベースのテンプレートを生成するための標準ライブラリです。HTML、XML、プレーンテキストなど、様々な形式のテキスト出力を動的に生成する際に使用されます。

主要な概念は以下の通りです。

  • Template: テンプレートの構造を表す型です。テンプレートは名前を持つことができ、その名前を使って他のテンプレートから参照したり、テンプレートセット内で識別したりします。
  • Parse: テンプレート文字列を解析し、Template オブジェクトを生成します。
  • Execute: 解析されたテンプレートにデータを適用し、結果を io.Writer に書き出します。
  • Name() メソッド: Template 型のメソッドで、テンプレートの名前を文字列として返します。テンプレートが作成される際に名前が指定されない場合、その名前は空文字列 ("") となります。

テンプレートの名前付けと空文字列

text/template パッケージでは、template.New("myTemplate") のようにしてテンプレートに名前を付けることができます。名前を付けない場合、または空文字列を名前として指定した場合、テンプレートの内部的な名前は空文字列として扱われます。

多くのGoプログラムでは、テンプレートの名前が空文字列であるかどうかをチェックしたり、空文字列のテンプレート名に特定の意味を持たせたりする慣習がありました。例えば、メインのテンプレートやデフォルトのテンプレートを空文字列の名前で識別する、といった使い方です。

技術的詳細

このコミットの技術的な詳細は、Go言語の text/template パッケージにおける Template 型の Name() メソッドの振る舞いの変更とその影響に集約されます。

以前の変更(Revert対象)

このコミットで元に戻された変更は、src/pkg/text/template/template.go ファイル内の Name() メソッドに以下のロジックを追加したものでした。

func (t *Template) Name() string {
	if t.name == "" {
		return "<unnamed>"
	}
	return t.name
}

この変更により、t.name (テンプレートの内部的な名前) が空文字列 ("") の場合、Name() メソッドは "<unnamed>" という固定文字列を返すようになりました。

変更の意図と問題点

意図:

  • デバッグやログ出力の際に、名前のないテンプレートがより明確に識別できるようにするため。
  • fmt.Printf("%q", t.Name()) のような形式で出力された場合に、"" ではなく "<unnamed>" と表示されることで、それが意図的に名前が付けられていないテンプレートであることが視覚的に分かりやすくなる。

問題点:

  • 後方互換性の破壊: 多くの既存のGoパッケージやアプリケーションは、Name() メソッドがテンプレートの実際の名前(空文字列を含む)を返すことを前提としていました。特に、テンプレート名が空文字列であるかどうかをチェックするロジックや、テンプレート名をキーとしてマップに格納するようなロジックを持つコードが影響を受けました。
  • 文字列比較の失敗: t.Name() == "" のような比較が、名前のないテンプレートに対して false を返すようになり、既存のロジックが期待通りに動作しなくなりました。
  • 予期せぬ動作: "<unnamed>" という文字列がテンプレート名として扱われることで、ファイルパスの構築、URLの生成、データベースのキーなど、テンプレート名が文字列として利用される様々な場面で予期せぬ動作を引き起こす可能性がありました。

このコミットによるRevert

このコミットは、上記のロジックを削除することで、Name() メソッドの振る舞いを元の状態に戻しました。

func (t *Template) Name() string {
	// 削除されたロジック:
	// if t.name == "" {
	// 	return "<unnamed>"
	// }
	return t.name
}

これにより、Name() メソッドは常に Template オブジェクトが保持する内部的な名前 (t.name) をそのまま返すようになります。テンプレートに名前が指定されていない場合は、"" が返されます。

影響

このRevertにより、text/template パッケージの Name() メソッドは以前の振る舞いに戻り、既存のGoプログラムとの後方互換性が維持されました。これにより、多くのパッケージが修正なしで引き続き動作できるようになりました。

Go言語の標準ライブラリは、後方互換性を非常に重視しています。このような小さな変更であっても、広範なエコシステムに与える影響を考慮し、互換性を維持するためにRevertが選択された典型的な例と言えます。

コアとなるコードの変更箇所

src/pkg/text/template/template.go ファイルの Name() メソッドが変更されました。

--- a/src/pkg/text/template/template.go
+++ b/src/pkg/text/template/template.go
@@ -40,9 +40,6 @@ func New(name string) *Template {
 
 // Name returns the name of the template.
 func (t *Template) Name() string {
-	if t.name == "" {
-		return "<unnamed>"
-	}
 	return t.name
 }
 

具体的には、以下の3行が削除されました。

	if t.name == "" {
		return "<unnamed>"
	}

コアとなるコードの解説

このコミットは、Template 型の Name() メソッドから、テンプレート名が空文字列 ("") の場合に "<unnamed>" を返すロジックを削除しています。

変更前のコード(Revert対象のコード)は以下のようになっていました。

func (t *Template) Name() string {
	if t.name == "" { // テンプレートの内部名が空文字列の場合
		return "<unnamed>" // "<unnamed>" を返す
	}
	return t.name // それ以外の場合は内部名をそのまま返す
}

このコミットによって、if t.name == "" { return "<unnamed>" } の部分が削除されたため、Name() メソッドは常に t.name の値をそのまま返すようになりました。

func (t *Template) Name() string {
	return t.name // 常に内部名をそのまま返す
}

これにより、テンプレートに名前が指定されていない場合(つまり t.name"" の場合)、Name() メソッドは "" を返すようになります。これは、この変更が加えられる前の text/template パッケージの元の振る舞いであり、多くの既存コードが依存していた動作です。

この変更は、Go言語の標準ライブラリにおける後方互換性の重要性を示しています。たとえデバッグ時の利便性を向上させるための小さな変更であっても、既存のコードベースに影響を与える可能性がある場合は、その変更を元に戻すことが優先されることがあります。

関連リンク

  • Go CL (Change List): https://golang.org/cl/7577044
    • これは、このコミットが対応するGoのコードレビューシステム(Gerrit)上の変更リストへのリンクです。Goプロジェクトでは、すべての変更はCLとして提出され、レビューを経てマージされます。

参考にした情報源リンク