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

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

このコミットは、Go言語の標準ライブラリ text/template パッケージにおける Template 型のメソッド Template の名称を Lookup に変更するものです。この変更は、特に html/template パッケージのように Template 型を埋め込む際に発生する命名の衝突や扱いにくさを解消することを目的としています。

コミット

commit e9025df7ad41d93c1c8943323db06bb49c8a16fe
Author: Rob Pike <r@golang.org>
Date:   Sat Nov 26 08:32:55 2011 -0800

    text/template: rename the method Template.Template to Template.Lookup
    Calling it Template makes it clumsy to embed the type, which html/template
    depends on.
    
    R=golang-dev, gri
    CC=golang-dev
    https://golang.org/cl/5432079

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

https://github.com/golang/go/commit/e9025df7ad41d93c1c8943323db06bb49c8a16fe

元コミット内容

text/template パッケージの Template 型に定義されていた、指定された名前のテンプレートを返すメソッド Template の名前を Lookup に変更しました。この変更に伴い、関連するテストコードも更新されています。

変更の背景

この変更の主な背景は、Go言語の構造体埋め込み(embedding)機能と、text/template パッケージの上に構築されている html/template パッケージの存在にあります。

Go言語では、ある構造体(型)を別の構造体に埋め込むことで、埋め込まれた型のメソッドを埋め込み先の型が直接利用できるようになります。この際、埋め込まれた型のメソッド名が、埋め込み先の型自身のメソッド名やフィールド名と衝突すると、予期せぬ挙動やコードの読みにくさを引き起こす可能性があります。

text/template パッケージの Template 型には、Template という名前のメソッドが存在していました。これは、Template 型のインスタンスから、そのインスタンスに関連付けられた別のテンプレート(名前で識別される)を取得するためのものでした。

一方、html/template パッケージは text/template パッケージを基盤として構築されており、内部で text/template.Template 型を埋め込んで利用しています。もし html/template パッケージの Template 型が、埋め込んだ text/template.Template 型の Template メソッドと同じ名前のメソッドやフィールドを持っていた場合、命名衝突が発生し、コードが「clumsy(扱いにくい、不格好)」になると判断されました。

この問題を回避し、将来的な拡張性やコードの明確性を保つために、Template.Template メソッドを Template.Lookup にリネームすることが決定されました。Lookup という名前は、特定の名前を持つ要素を「検索する」というメソッドの機能により合致しており、命名衝突のリスクも低減されます。

前提知識の解説

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

text/templateパッケージは、Go言語の標準ライブラリの一部であり、テキストベースのテンプレートを生成するための機能を提供します。これは、動的にコンテンツを生成する際に非常に便利で、例えばWebページのHTML、設定ファイル、コード生成などに利用されます。

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

  • Template: テンプレートのコンパイル済み表現を表す型です。この型を通じて、テンプレートの解析、実行、関連するテンプレートの管理などが行われます。
  • テンプレートの定義: テンプレートは、{{...}}で囲まれたアクション(例: {{.Name}}でデータ構造のフィールドにアクセス、{{range .Items}}...{{end}}でループ処理、{{if .Condition}}...{{end}}で条件分岐)と、プレーンテキストから構成されます。
  • 名前付きテンプレート: 一つのTemplateインスタンスは、複数の名前付きテンプレートを保持できます。これにより、共通のレイアウトや部分的なテンプレートを再利用できます。例えば、{{template "header"}}のように別のテンプレートを呼び出すことができます。

Go言語の構造体埋め込み(Embedding)

Go言語の構造体埋め込みは、他のプログラミング言語における継承に似た機能ですが、よりシンプルで柔軟な「コンポジション(合成)」のメカニズムです。ある構造体の中に、フィールド名なしで別の構造体を宣言することで、埋め込まれた構造体のフィールドやメソッドが、埋め込み先の構造体のものとして「昇格(promoted)」されます。

例:

type Base struct {
    ID int
}

func (b Base) GetID() int {
    return b.ID
}

type Derived struct {
    Base // Base構造体を埋め込み
    Name string
}

func main() {
    d := Derived{Base: Base{ID: 1}, Name: "Test"}
    fmt.Println(d.GetID()) // Derived型からBase型のGetIDメソッドを直接呼び出せる
}

この機能はコードの再利用性を高めますが、埋め込まれた型のメソッド名と埋め込み先の型のメソッド名が同じ場合、埋め込み先の型のメソッドが優先されます。これが、今回のコミットで問題となった「clumsy」な状況を引き起こす可能性がありました。

html/templateパッケージ

html/templateパッケージは、text/templateパッケージを基盤として構築されており、HTML出力に特化したテンプレート機能を提供します。最も重要な違いは、html/templateが自動的にコンテキストに応じたエスケープ処理を行う点です。これにより、クロスサイトスクリプティング(XSS)などのセキュリティ脆弱性を防ぐことができます。

html/templateTemplate型は、内部でtext/templateTemplate型を埋め込んでおり、text/templateの基本的な機能を利用しつつ、HTML特有のセキュリティ機能を追加しています。

技術的詳細

このコミットは、text/templateパッケージ内のTemplate構造体のメソッドTemplate(name string) *TemplateLookup(name string) *Templateにリネームするという、比較的単純ながらも重要な変更です。

変更前:

// Template returns the template with the given name that is associated with t,
// or nil if there is no such template.
func (t *Template) Template(name string) *Template {
	return t.tmpl[name]
}

変更後:

// Lookup returns the template with the given name that is associated with t,
// or nil if there is no such template.
func (t *Template) Lookup(name string) *Template {
	return t.tmpl[name]
}

このリネームにより、Template型を埋め込む他の型(特にhtml/template.Template)が、自身のTemplateという名前のメソッドやフィールドを持つ場合に、名前の衝突を避けることができます。

例えば、html/templateパッケージのTemplate型が、text/template.Templateを埋め込みつつ、自身もTemplateという名前のメソッド(例えば、テンプレート自体を返すようなメソッド)を持っていたとします。

変更前:

type HTMLTemplate struct {
    *text_template.Template // text/template.Templateを埋め込み
    // ...
}

// もしHTMLTemplateが独自のTemplateメソッドを持っていた場合
func (ht *HTMLTemplate) Template() *HTMLTemplate {
    // ...
}

// この場合、ht.Template("name") と ht.Template() の呼び出しが曖昧になるか、
// 埋め込み先のメソッドが優先されて意図しない挙動になる可能性がある。

変更後:

type HTMLTemplate struct {
    *text_template.Template // text/template.Templateを埋め込み
    // ...
}

// HTMLTemplateが独自のTemplateメソッドを持っていても問題ない
func (ht *HTMLTemplate) Template() *HTMLTemplate {
    // ...
}

// テンプレートのルックアップは ht.Lookup("name") と明確に呼び出せる

このように、Lookupというより具体的な名前に変更することで、メソッドの役割が明確になり、Goの構造体埋め込みのセマンティクスとより調和するようになりました。これは、APIの設計における一貫性と将来的な保守性を向上させるための良いプラクティスです。

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

このコミットによるコードの変更は、以下の2つのファイルにわたります。

  1. src/pkg/text/template/exec_test.go
  2. src/pkg/text/template/template.go

src/pkg/text/template/exec_test.go

このファイルはtext/templateパッケージの実行に関するテストコードです。 変更内容は、Template.Templateメソッドの呼び出しをTemplate.Lookupに置き換えるものです。

--- a/src/pkg/text/template/exec_test.go
+++ b/src/pkg/text/template/exec_test.go
@@ -677,7 +677,7 @@ func TestTree(t *testing.T) {
 	}\n \tconst expect = \"[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]\"\n \t// First by looking up the template.\n-\terr = tmpl.Template(\"tree\").Execute(&b, tree)\n+\terr = tmpl.Lookup(\"tree\").Execute(&b, tree)\n \tif err != nil {\n \t\tt.Fatal(\"exec error:\", err)\n \t}\n```

### `src/pkg/text/template/template.go`

このファイルは`text/template`パッケージの主要な定義が含まれています。
変更内容は、`Template`構造体の`Template`メソッドの定義を`Lookup`にリネームするものです。

```diff
--- a/src/pkg/text/template/template.go
+++ b/src/pkg/text/template/template.go
@@ -136,9 +136,9 @@ func (t *Template) Funcs(funcMap FuncMap) *Template {
 	return t
 }\n \n-// Template returns the template with the given name that is associated with t,\n+// Lookup returns the template with the given name that is associated with t,\n // or nil if there is no such template.\n-func (t *Template) Template(name string) *Template {\n+func (t *Template) Lookup(name string) *Template {\n \treturn t.tmpl[name]\n }\n \n```

## コアとなるコードの解説

このコミットの核心は、`src/pkg/text/template/template.go`ファイルにおける`Template`構造体のメソッドのリネームです。

変更前:

```go
func (t *Template) Template(name string) *Template {
	return t.tmpl[name]
}

このメソッドは、Template型のレシーバtに対して呼び出され、引数nameで指定された名前のテンプレートを、内部のマップt.tmplから検索して返していました。メソッド名が型名と同じTemplateであったため、html/templateのような他のパッケージがtext/template.Templateを埋め込む際に、命名衝突の可能性がありました。

変更後:

func (t *Template) Lookup(name string) *Template {
	return t.tmpl[name]
}

メソッド名がLookupに変更されたことで、その機能(名前による検索)がより明確になり、かつ型名との衝突がなくなりました。これにより、Template型を埋め込む他の型が、自身のTemplateという名前のメソッドやフィールドを持っていても、曖昧さなくLookupメソッドを呼び出すことができるようになります。

src/pkg/text/template/exec_test.goの変更は、このメソッド名のリネームに伴うテストコードの修正です。メソッド名が変わったため、テストコード内の呼び出し箇所もそれに合わせて更新されています。これは、リファクタリングにおける一般的な手順であり、変更が正しく反映され、既存の機能が損なわれていないことを確認するために不可欠です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Go言語の構造体埋め込みに関する一般的な解説記事
  • コミットメッセージに記載されているGoのコードレビューシステム(Gerrit)のリンク: https://golang.org/cl/5432079 (現在はGoのGerritインスタンスはGitHubに移行しているため、直接アクセスしても情報が得られない可能性がありますが、当時の変更履歴を追うための参照情報です。)