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

[インデックス 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 に変換されていなかったりする問題がありました。

コミットメッセージによると、以下の点が問題視されていました。

  1. ドキュメントとの不一致: テンプレートの再定義に関するドキュメントの記述と、実際の ParseParseInSet メソッドの挙動が異なっていた。
  2. エラーハンドリングの不整合:
    • 通常、テンプレートの再定義はエラーとして扱われるべきだが、一部のコードパスでは panic が発生していた。
    • 特に Add メソッドを明示的に呼び出す場合、それはプログラマーの意図的なバグである可能性が高いため panic が適切であると判断されていた。しかし、それ以外の再定義のケースでは error を返すのが適切であるにもかかわらず、panic が発生するケースが存在した。
  3. コードの複雑性: 上記の不整合を引き起こしていた「問題のある関数」(offending function)が存在し、それがコードの明確性を損なっていた。

このコミットは、これらの問題を解決し、テンプレートの再定義時の挙動をより予測可能で一貫性のあるものにすることを目指しています。特に、panicerror の使い分けを明確にし、コードのシンプル化を図っています。

前提知識の解説

このコミットを理解するためには、以下の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 パッケージでは、同じ名前のテンプレートを複数回定義しようとすると、「再定義」が発生します。このコミット以前は、この再定義の挙動が状況によって異なり、一貫性がありませんでした。特に、ParseInSetParse メソッドを通じて再定義が行われた場合と、Add メソッドを直接呼び出して再定義が行われた場合で、エラーハンドリングが異なっていました。

このコミットは、この再定義時のエラーハンドリングを統一し、ドキュメントに沿った挙動を実現することを目指しています。

技術的詳細

このコミットは、src/pkg/text/template/parse.gosrc/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.」とあり、Addpanic を起こした場合にそれをエラーに変換する意図があったことが示唆されています。

    このコミットでは、この 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
    }
    

    この変更により、ParseInSetset.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 パッケージにおけるテンプレートの再定義時の挙動が統一されました。

  • ParseInSetParse メソッドを通じてテンプレートを再定義しようとすると、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.