[インデックス 10286] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージにおけるテンプレートの再定義時の挙動を修正し、一貫性を持たせることを目的としています。特に、テンプレートセット内でのテンプレートの再定義が、ドキュメントの記述と実際の挙動が異なる点を修正し、エラーハンドリングを改善しています。
コミット
- Author: Rob Pike r@golang.org
- Date: Tue Nov 8 14:33:07 2011 -0800
- Commit Message:
text/template: make redefinition of a template in a set more consistent. Also make it agree with the documentation. You get an error, unless you're calling Add explicitly, in which case it panics since that's almost certainly a bug. The discrepancy was caused by a panic that wasn't turned into an error along one path; deleted the offending function for clarity. R=r, rsc CC=golang-dev https://golang.org/cl/5354045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3e94e757eff3bfa4150b1e88fda8db98905290de
元コミット内容
text/template: make redefinition of a template in a set more consistent.
Also make it agree with the documentation. You get an error, unless you're
calling Add explicitly, in which case it panics since that's almost certainly
a bug. The discrepancy was caused by a panic that wasn't turned into
an error along one path; deleted the offending function for clarity.
R=r, rsc
CC=golang-dev
https://golang.org/cl/5354045
変更の背景
このコミットの背景には、Go言語の text/template
パッケージにおけるテンプレートの再定義時の挙動の不整合がありました。具体的には、テンプレートセット(Set
)に既に存在する名前のテンプレートを再度定義しようとした際に、その挙動がドキュメントと一致していなかったり、特定のパスで panic
が発生するにもかかわらず、別のパスでは error
に変換されていなかったりする問題がありました。
コミットメッセージによると、以下の点が問題視されていました。
- ドキュメントとの不一致: テンプレートの再定義に関するドキュメントの記述と、実際の
Parse
やParseInSet
メソッドの挙動が異なっていた。 - エラーハンドリングの不整合:
- 通常、テンプレートの再定義はエラーとして扱われるべきだが、一部のコードパスでは
panic
が発生していた。 - 特に
Add
メソッドを明示的に呼び出す場合、それはプログラマーの意図的なバグである可能性が高いためpanic
が適切であると判断されていた。しかし、それ以外の再定義のケースではerror
を返すのが適切であるにもかかわらず、panic
が発生するケースが存在した。
- 通常、テンプレートの再定義はエラーとして扱われるべきだが、一部のコードパスでは
- コードの複雑性: 上記の不整合を引き起こしていた「問題のある関数」(
offending function
)が存在し、それがコードの明確性を損なっていた。
このコミットは、これらの問題を解決し、テンプレートの再定義時の挙動をより予測可能で一貫性のあるものにすることを目指しています。特に、panic
と error
の使い分けを明確にし、コードのシンプル化を図っています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と text/template
パッケージの基本的な知識が必要です。
1. Go言語のエラーハンドリング (error
vs panic
)
Go言語では、エラーハンドリングに主に2つのメカニズムがあります。
error
: 予期されるが回復可能な問題(例: ファイルが見つからない、ネットワーク接続がタイムアウトする)に対して使用されます。関数はerror
型の値を返します。呼び出し元はif err != nil
のパターンでエラーをチェックし、適切に処理します。これはGo言語における一般的なエラーハンドリングの推奨される方法です。panic
: 予期されない、回復不可能な問題(例: nilポインタのデリファレンス、配列の範囲外アクセス)に対して使用されます。panic
が発生すると、現在の関数の実行が停止し、defer関数が実行された後、呼び出し元の関数に制御が戻り、最終的にプログラム全体がクラッシュします。panic
は通常、プログラムのバグを示すものであり、通常の制御フローでは処理できないような致命的な状況で使用されます。
このコミットでは、テンプレートの再定義が「エラー」として扱われるべきか、「バグ」として panic
すべきか、という判断基準が変更されています。
2. text/template
パッケージ
text/template
パッケージは、Go言語でテキストベースのテンプレートを生成するための標準ライブラリです。HTML、XML、プレーンテキストなど、様々な形式のテキストを動的に生成するのに使用されます。
主要な概念は以下の通りです。
Template
: 個々のテンプレートを表す構造体です。名前を持ち、テンプレートの定義(構文解析されたツリー)を含みます。Set
: 複数のTemplate
オブジェクトを管理するためのコンテナです。名前によってテンプレートを識別し、セット内のテンプレート間で関数や変数を共有できます。Parse
メソッド: テンプレートの定義文字列を解析し、Template
オブジェクトを生成します。ParseInSet
メソッド: テンプレートを解析し、指定されたSet
に追加します。Add
メソッド: 既に解析されたTemplate
オブジェクトをSet
に追加します。
3. テンプレートの再定義
text/template
パッケージでは、同じ名前のテンプレートを複数回定義しようとすると、「再定義」が発生します。このコミット以前は、この再定義の挙動が状況によって異なり、一貫性がありませんでした。特に、ParseInSet
や Parse
メソッドを通じて再定義が行われた場合と、Add
メソッドを直接呼び出して再定義が行われた場合で、エラーハンドリングが異なっていました。
このコミットは、この再定義時のエラーハンドリングを統一し、ドキュメントに沿った挙動を実現することを目指しています。
技術的詳細
このコミットは、src/pkg/text/template/parse.go
と src/pkg/text/template/set.go
の2つのファイルにわたる変更を含んでいます。主な変更点は、テンプレートの再定義時のエラーハンドリングロジックの統一と、それに伴うコードの簡素化です。
src/pkg/text/template/parse.go
の変更
このファイルでは、Template
型の ParseInSet
メソッドが変更されています。
-
ParseInSet
メソッドのコメント変更: 変更前:// to the set.
変更後:// to the set. It is an error if s is already defined in the set.
この変更により、ParseInSet
がテンプレートをセットに追加する際に、既に定義されている場合はエラーを返すことが明示されました。これは、このコミットの目的である「ドキュメントとの一致」を反映しています。 -
addToSet
関数の削除: 変更前は、Template
型にaddToSet
というプライベートメソッドが存在し、これがテンプレートをセットに追加する役割を担っていました。この関数は、セットがnil
でない場合にset.Add(t)
を呼び出していました。また、コメントには「If double-assigned, Add will panic and we will turn that into an error.」とあり、Add
がpanic
を起こした場合にそれをエラーに変換する意図があったことが示唆されています。このコミットでは、この
addToSet
関数が完全に削除されました。 -
ParseInSet
メソッド内のロジック変更:addToSet
の削除に伴い、ParseInSet
メソッド内で直接set.add(t)
が呼び出されるようになりました。 変更前:t.addToSet(set) return t, nil } // addToSet adds the template to the set, verifying it's not being double-assigned. func (t *Template) addToSet(set *Set) { if set == nil || t.set == set { return } // If double-assigned, Add will panic and we will turn that into an error. set.Add(t) }
変更後:
if set != nil { err = set.add(t) } return t, err }
この変更により、
ParseInSet
はset.add(t)
の戻り値であるerror
を直接返すようになりました。これにより、テンプレートの再定義がParseInSet
経由で行われた場合、panic
ではなくerror
が返されるようになり、エラーハンドリングが一貫しました。
src/pkg/text/template/set.go
の変更
このファイルでは、Set
型の Parse
メソッドが変更されています。
-
Parse
メソッドのコメント変更: 変更前:// to the set. If a template is redefined, the element in the set is\n// overwritten with the new definition.
変更後:// to the set. It is an error if a template has a name already defined in the set.
この変更は、Parse
メソッドがテンプレートをセットに追加する際に、既に定義されている場合はエラーを返すことを明確にしています。変更前は「上書きされる」と記述されていましたが、変更後は「エラーになる」と明記され、挙動の変更が反映されています。 -
Parse
メソッド内のロジック変更: 変更前は、Parse
メソッド内で解析された各テンプレートに対してtmpl.addToSet(s)
を呼び出し、その後s.tmpl[name] = tmpl
でセットに直接追加していました。 変更後:for name, tree := range trees { tmpl := New(name) tmpl.Tree = tree err = s.add(tmpl) // 直接 s.add を呼び出し、エラーをチェック if err != nil { return s, err } } return s, nil
この変更により、
Parse
メソッドもs.add(tmpl)
の戻り値であるerror
を直接チェックし、エラーが発生した場合はすぐにそのエラーを返すようになりました。これにより、Parse
メソッド経由でのテンプレートの再定義もerror
として扱われるようになり、text/template
パッケージ全体でのエラーハンドリングの一貫性が向上しました。
全体的な影響
これらの変更により、text/template
パッケージにおけるテンプレートの再定義時の挙動が統一されました。
ParseInSet
やParse
メソッドを通じてテンプレートを再定義しようとすると、error
が返されるようになりました。これは、通常のプログラミングフローで予期されるエラーとして処理されるべきです。Set.Add
メソッドを直接呼び出してテンプレートを再定義しようとした場合、コミットメッセージにあるように「それはほぼ確実にバグであるためpanic
する」という挙動が維持されます。これは、Add
が低レベルな操作であり、プログラマーが意図的に重複を追加しようとした場合は、それがバグである可能性が高いという設計思想に基づいています。addToSet
という中間関数の削除により、コードのパスが簡素化され、エラーハンドリングのロジックがより明確になりました。
この変更は、text/template
パッケージの堅牢性と使いやすさを向上させ、開発者がテンプレートの再定義時の挙動をより正確に予測できるようにすることを目的としています。
コアとなるコードの変更箇所
diff --git a/src/pkg/text/template/parse.go b/src/pkg/text/template/parse.go
index 2fbd37ffa9..6ecd2f50b4 100644
--- a/src/pkg/text/template/parse.go
+++ b/src/pkg/text/template/parse.go
@@ -71,7 +71,7 @@ func (t *Template) Parse(s string) (tmpl *Template, err error) {
// ParseInSet parses the template definition string to construct an internal
// representation of the template for execution. It also adds the template
-// to the set.
+// to the set. It is an error if s is already defined in the set.
// Function bindings are checked against those in the set.
func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err error) {
var setFuncs FuncMap
@@ -82,15 +82,8 @@ func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err error) {
if err != nil {
return nil, err
}
-\tt.addToSet(set)
-\treturn t, nil
-}\n-\n-// addToSet adds the template to the set, verifying it's not being double-assigned.\n-func (t *Template) addToSet(set *Set) {\n-\tif set == nil || t.set == set {\n-\t\treturn\n+\tif set != nil {\n+\t\terr = set.add(t)
\t}\n-\t// If double-assigned, Add will panic and we will turn that into an error.\n-\tset.Add(t)\n+\treturn t, err
}\ndiff --git a/src/pkg/text/template/set.go b/src/pkg/text/template/set.go
index bd0dfc6b36..ba5dc00544 100644
--- a/src/pkg/text/template/set.go
+++ b/src/pkg/text/template/set.go
@@ -101,8 +101,7 @@ func (s *Set) Execute(wr io.Writer, name string, data interface{}) error {\n
// Parse parses a string into a set of named templates. Parse may be called
// multiple times for a given set, adding the templates defined in the string
// to the set. If a template is redefined, the element in the set is
-// overwritten with the new definition.\n+// to the set. It is an error if a template has a name already defined in the set.\n func (s *Set) Parse(text string) (*Set, error) {\n \ttrees, err := parse.Set(text, s.leftDelim, s.rightDelim, s.parseFuncs, builtins)\n \tif err != nil {\n@@ -112,8 +111,10 @@ func (s *Set) Parse(text string) (*Set, error) {\n \tfor name, tree := range trees {\n \t\ttmpl := New(name)\n \t\ttmpl.Tree = tree\n-\t\ttmpl.addToSet(s)\n-\t\ts.tmpl[name] = tmpl\n+\t\terr = s.add(tmpl)\n+\t\tif err != nil {\n+\t\t\treturn s, err\n+\t\t}\n \t}\n \treturn s, nil\n }\n```
## コアとなるコードの解説
### `src/pkg/text/template/parse.go` の変更点
1. **`ParseInSet` メソッドのコメント更新**:
`// to the set.` から `// to the set. It is an error if s is already defined in the set.` へ変更されました。これは、`ParseInSet` がテンプレートをセットに追加する際に、既に同じ名前のテンプレートが存在する場合はエラーを返すという新しい挙動を明確に示しています。
2. **`addToSet` 関数の削除**:
`Template` 型に存在したプライベートメソッド `addToSet` が完全に削除されました。この関数は、テンプレートをセットに追加する際の中間層として機能していましたが、そのロジックが `ParseInSet` および `Set.Parse` に直接統合されることになりました。これにより、コードの階層が浅くなり、処理の流れがより直接的になりました。
3. **`ParseInSet` メソッド内のロジック変更**:
変更前は `t.addToSet(set)` を呼び出していましたが、変更後は `if set != nil { err = set.add(t) }` となり、`set.add(t)` の戻り値である `error` を直接 `err` 変数に代入し、それを `ParseInSet` の戻り値として返すようになりました。
この変更の核心は、テンプレートの再定義が `ParseInSet` 経由で行われた場合に、以前のように `addToSet` 内部で `panic` が発生し、それをエラーに変換するような複雑なパスではなく、`set.add` が直接 `error` を返すことで、よりシンプルかつGo言語の慣習に沿ったエラーハンドリングを実現している点です。
### `src/pkg/text/template/set.go` の変更点
1. **`Parse` メソッドのコメント更新**:
`If a template is redefined, the element in the set is overwritten with the new definition.` から `It is an error if a template has a name already defined in the set.` へ変更されました。これは、`Set.Parse` メソッドがテンプレートを解析してセットに追加する際に、既に同じ名前のテンプレートが存在する場合は上書きするのではなく、エラーを返すという新しい挙動を明確に示しています。
2. **`Parse` メソッド内のロジック変更**:
変更前は、`for` ループ内で `tmpl.addToSet(s)` を呼び出し、その後 `s.tmpl[name] = tmpl` でセットにテンプレートを追加していました。
変更後:
```go
err = s.add(tmpl) // 直接 s.add を呼び出し、エラーをチェック
if err != nil {
return s, err
}
```
この変更により、`Set.Parse` メソッドも、解析された各テンプレートをセットに追加する際に `s.add(tmpl)` を直接呼び出し、その戻り値である `error` を即座にチェックするようになりました。エラーが発生した場合は、ループを中断してそのエラーを返します。これにより、`Set.Parse` 経由でのテンプレートの再定義も `error` として扱われるようになり、`text/template` パッケージ全体でのエラーハンドリングの一貫性がさらに強化されました。
これらの変更は、`text/template` パッケージの内部実装を簡素化し、テンプレートの再定義に関する挙動をより予測可能で、Go言語のエラーハンドリングの慣習に沿ったものにしています。
## 関連リンク
* Go CL 5354045: [https://golang.org/cl/5354045](https://golang.org/cl/5354045)
## 参考にした情報源リンク
* Go言語公式ドキュメント: `text/template` パッケージ ([https://pkg.go.dev/text/template](https://pkg.go.dev/text/template))
* Go言語におけるエラーハンドリングの慣習に関する情報 (例: Go公式ブログやEffective Goなど)
* A Tour of Go: Errors ([https://go.dev/tour/basics/16](https://go.dev/tour/basics/16))
* Effective Go: Errors ([https://go.dev/doc/effective_go#errors](https://go.dev/doc/effective_go#errors))
* Go Blog: Error handling and Go ([https://go.dev/blog/error-handling-and-go](https://go.dev/blog/error-handling-and-go))
* Go言語における `panic` と `recover` の使用に関する情報
* Go Blog: Defer, Panic, and Recover ([https://go.dev/blog/defer-panic-and-recover](https://go.dev/blog/defer-panic-and-recover))
* GitHubのコミット履歴とdiffビューア
* Go言語のソースコード (`src/pkg/text/template/`)I have generated the commit explanation as requested.