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

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

このコミットは、Go言語の標準ライブラリである html/template パッケージに対する変更です。具体的には、以下の2つのファイルが変更されています。

  • src/pkg/html/template/escape_test.go: html/template パッケージのテストファイル。
  • src/pkg/html/template/template.go: html/template パッケージの主要な実装ファイル。

変更の目的は、html/template パッケージの公開APIが text/template パッケージに直接依存しないようにすることです。

コミット

commit 1402d1a68615d037365158578b0c4861e0fb4157
Author: Rob Pike <r@golang.org>
Date:   Wed Dec 14 11:22:17 2011 -0800

    html/template: define the FuncMap type locally
    This redefinition means that the public signature of html/template
    does not refer to text/template.

    Fixes #2546.

    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5487083

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

https://github.com/golang/go/commit/1402d1a68615d037365158578b0c4861e0fb4157

元コミット内容

html/template: define the FuncMap type locally
This redefinition means that the public signature of html/template
does not refer to text/template.

Fixes #2546.

変更の背景

Go言語の html/template パッケージは、HTML出力の自動エスケープ機能を提供するテンプレートエンジンです。これは、基本的なテンプレート機能を提供する text/template パッケージの上に構築されています。

このコミットが行われる前は、html/template パッケージの Funcs メソッドが text/template.FuncMap 型を直接引数として受け取っていました。これは、html/template の公開APIが text/template パッケージの型に直接依存していることを意味します。

ソフトウェア設計において、モジュール間の結合度(Coupling)は低い方が望ましいとされています。結合度が高いと、一方のモジュールが変更された際に、それに依存する他のモジュールも変更が必要になる可能性が高まります。この場合、html/templatetext/template.FuncMap に直接依存しているため、text/templateFuncMap 型の定義が変更された場合、html/template もそれに合わせて変更する必要が生じる可能性があります。

このコミットの背景にあるのは、このような不必要な依存関係を解消し、html/template パッケージの独立性を高めることです。コミットメッセージにある「public signature of html/template does not refer to text/template」という記述は、この目的を明確に示しています。これにより、html/templatetext/template の内部実装の詳細からより疎結合になり、将来的な変更に対する堅牢性が向上します。

Fixes #2546 という記述がありますが、このIssueはGoの公開Issueトラッカーでは直接見つかりませんでした。これは、内部的なIssueトラッカーの番号であるか、非常に古いIssueである可能性があります。しかし、コミットメッセージとコードの変更内容から、その目的は明確です。

前提知識の解説

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

text/template パッケージは、Go言語におけるテキストベースのテンプレートエンジンを提供します。これは、プレースホルダーを含むテキストを解析し、データ構造から提供された値でそれらを置き換えることができます。主な機能には以下があります。

  • テンプレートの解析と実行: テンプレート文字列を解析し、指定されたデータ構造を基に最終的なテキストを生成します。
  • アクション: {{.FieldName}} のような構文でデータ構造のフィールドにアクセスしたり、{{range .Slice}}...{{end}} でループ処理を行ったり、{{if .Condition}}...{{end}} で条件分岐を行ったりできます。
  • 関数: テンプレート内でカスタム関数を呼び出すことができます。これらの関数は FuncMap を介してテンプレートに登録されます。

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

html/template パッケージは、text/template パッケージを基盤としていますが、HTML出力に特化しています。最も重要な機能は、コンテキストに応じた自動エスケープです。これにより、クロスサイトスクリプティング(XSS)などのセキュリティ脆弱性を防ぐことができます。例えば、ユーザーが入力した文字列をHTMLページに表示する際に、悪意のあるスクリプトが埋め込まれていても、html/template が自動的にエスケープ処理を行うため、スクリプトが実行されるのを防ぎます。

FuncMap とは

FuncMap は、テンプレート内で使用できるカスタム関数を登録するためのマップ型です。キーは関数の名前(文字列)、値はGoの関数(interface{} 型)です。これらの関数は、テンプレートの実行時に呼び出され、動的なコンテンツ生成に利用されます。

text/template パッケージには text/template.FuncMap という型が定義されており、これは map[string]interface{} のエイリアスです。

型のエイリアスと依存関係

Go言語では、type NewType OldType のようにして既存の型に新しい名前(エイリアス)を付けることができます。このコミットでは、html/template パッケージ内で FuncMap という新しい型を定義していますが、その基底型は map[string]interface{} であり、これは text/template.FuncMap と同じ基底型です。

重要なのは、html/templatetext/template.FuncMap を直接参照する代わりに、独自の FuncMap 型を定義することで、html/template の公開APIから text/template への直接的な型依存を排除している点です。これにより、html/template を利用する側は、text/template パッケージをインポートすることなく、html/templateFuncs メソッドを利用できるようになります。

技術的詳細

このコミットの核心は、html/template パッケージが text/template パッケージの FuncMap 型に直接依存するのをやめ、独自の FuncMap 型を定義した点にあります。

変更前は、html/templateFuncs メソッドのシグネチャは以下のようになっていました。

func (t *Template) Funcs(funcMap template.FuncMap) *Template

ここで template.FuncMaptext/template パッケージの FuncMap を指します。このため、html/template を利用するユーザーは、Funcs メソッドを呼び出す際に text/template をインポートし、その FuncMap 型を使用する必要がありました。

このコミットでは、html/template パッケージ内に FuncMap という新しい型を定義しました。

type FuncMap map[string]interface{}

この新しい FuncMap 型は、text/template.FuncMap と同じ基底型 map[string]interface{} を持ちます。これにより、html/templateFuncs メソッドのシグネチャは以下のように変更されました。

func (t *Template) Funcs(funcMap FuncMap) *Template

この変更により、html/template の公開APIから text/template への直接的な型依存がなくなりました。html/template を利用する側は、html/template パッケージが提供する FuncMap 型を使用すればよくなり、text/template を明示的にインポートする必要がなくなります。

しかし、html/template は内部的には text/template を利用しています。そのため、html/templateFuncs メソッドの実装では、受け取った html/template.FuncMap 型の値を text/template.FuncMap 型に型変換して、内部の text/template.Template オブジェクトの Funcs メソッドに渡す必要があります。これは、t.text.Funcs(template.FuncMap(funcMap)) という行で行われています。ここで template.FuncMap(funcMap) は、html/templateFuncMap 型の値を text/templateFuncMap 型に変換しています。Go言語では、基底型が同じであれば、このように型変換を行うことができます。

この変更は、html/template パッケージのAPI設計をよりクリーンにし、ユーザーが html/template を使う際に text/template の詳細を意識する必要をなくすという点で重要です。これは、Goの標準ライブラリがモジュール性と独立性を重視していることの一例と言えます。

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

src/pkg/html/template/escape_test.go

--- a/src/pkg/html/template/escape_test.go
+++ b/src/pkg/html/template/escape_test.go
@@ -654,7 +654,7 @@ func TestEscape(t *testing.T) {
 	for _, test := range tests {
 		tmpl := New(test.name)
 		// TODO: Move noescape into template/func.go
-		tmpl.Funcs(template.FuncMap{
+		tmpl.Funcs(FuncMap{
 			"noescape": func(a ...interface{}) string {
 				return fmt.Sprint(a...)
 			},
@@ -792,7 +792,7 @@ func TestEscapeSet(t *testing.T) {

 	// pred is a template function that returns the predecessor of a
 	// natural number for testing recursive templates.
-	fns := template.FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
+	fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
 		if len(a) == 1 {
 			if i, _ := a[0].(int); i > 0 {
 				return i - 1, nil

このテストファイルでは、tmpl.Funcs() および fns の初期化において、template.FuncMap の代わりに新しく定義された FuncMap 型が使用されるように変更されています。これは、APIの変更に合わせてテストコードも更新されたことを示しています。

src/pkg/html/template/template.go

--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -154,12 +154,20 @@ func (t *Template) Name() string {
 	return t.text.Name()
 }

+// FuncMap is the type of the map defining the mapping from names to
+// functions. Each function must have either a single return value, or two
+// return values of which the second has type error. In that case, if the
+// second (error) argument evaluates to non-nil during execution, execution
+// terminates and Execute returns that error. FuncMap has the same base type
+// as template.FuncMap, copied here so clients need not import "text/template".
+type FuncMap map[string]interface{}
+
 // Funcs adds the elements of the argument map to the template's function map.
 // It panics if a value in the map is not a function with appropriate return
 // type. However, it is legal to overwrite elements of the map. The return
 // value is the template, so calls can be chained.
-func (t *Template) Funcs(funcMap template.FuncMap) *Template {
-	t.text.Funcs(funcMap)
+func (t *Template) Funcs(funcMap FuncMap) *Template {
+	t.text.Funcs(template.FuncMap(funcMap))
 	return t
 }

このファイルが変更の主要な部分です。

  1. FuncMap 型の定義追加: type FuncMap map[string]interface{} という行が追加され、html/template パッケージ内で独自の FuncMap 型が定義されました。この定義には、その目的と text/template.FuncMap との関係を説明するコメントも追加されています。

  2. Funcs メソッドのシグネチャ変更: func (t *Template) Funcs(funcMap template.FuncMap) *Templatefunc (t *Template) Funcs(funcMap FuncMap) *Template に変更されました。これにより、Funcs メソッドが html/template 独自の FuncMap 型を引数として受け取るようになりました。

  3. Funcs メソッドの実装変更: t.text.Funcs(funcMap)t.text.Funcs(template.FuncMap(funcMap)) に変更されました。これは、html/templateFuncs メソッドが受け取った html/template.FuncMap 型の値を、内部で利用している text/templateFuncs メソッドに渡す前に、text/template.FuncMap 型に明示的に型変換していることを示しています。

コアとなるコードの解説

このコミットのコアとなる変更は、html/template/template.go における FuncMap 型のローカル定義と、それに伴う Funcs メソッドのシグネチャおよび実装の変更です。

  1. type FuncMap map[string]interface{} の追加: この行は、html/template パッケージ内に FuncMap という新しい型を定義しています。この型は map[string]interface{} のエイリアスであり、text/template.FuncMap と同じ基底型を持ちます。これにより、html/templatetext/templateFuncMap を直接参照することなく、独自の FuncMap 型を公開APIとして提供できるようになります。追加されたコメントは、この型の目的と、クライアントが text/template をインポートする必要がないことを明確に説明しています。

  2. func (t *Template) Funcs(funcMap FuncMap) *Template へのシグネチャ変更: Template 型の Funcs メソッドが、text/template.FuncMap ではなく、新しく定義された html/template.FuncMap を引数として受け取るように変更されました。これは、html/template の公開APIが text/template から独立したことを意味します。これにより、html/template を利用する開発者は、text/template パッケージを意識することなく、html/template の機能を利用できるようになります。

  3. t.text.Funcs(template.FuncMap(funcMap)) への実装変更: html/templateTemplate オブジェクトは、内部に text/template.Template オブジェクト (t.text) を持っています。html/templateFuncs メソッドが呼び出された際、その機能は最終的に内部の text/template.Template オブジェクトの Funcs メソッドに委譲されます。 この変更では、html/template.FuncMap 型の funcMap 引数を、text/template.FuncMap(funcMap) という形で text/template.FuncMap 型に明示的に型変換しています。Go言語では、基底型が同じであれば、このように型変換を行うことができます。この変換により、html/template は自身の公開APIの独立性を保ちつつ、内部的には text/template の機能を引き続き利用することができます。

これらの変更により、html/template パッケージは、その公開APIにおいて text/template パッケージへの直接的な依存を解消し、よりクリーンで独立した設計を実現しています。これは、Go言語の標準ライブラリが、パッケージ間の適切な分離と疎結合を重視していることを示す良い例です。

関連リンク

  • Go言語の公式ドキュメント: text/template パッケージと html/template パッケージに関する詳細な情報が提供されています。
  • Go言語のIssueトラッカー: コミットメッセージに Fixes #2546 とありますが、このIssueは公開されているGoのIssueトラッカーでは直接見つかりませんでした。これは、内部的なIssueであるか、非常に古いIssueである可能性があります。

参考にした情報源リンク