[インデックス 14944] ファイルの概要
このコミットは、Go言語の text/template/parse
パッケージにおけるエラーメッセージの改善を目的としています。具体的には、テンプレートのパース時に発生するエラーメッセージから、ユーザーにとって理解しにくい内部的な itemType
の表示を削除し、より人間が読みやすい形式に変更しています。これにより、エラーメッセージが直感的になり、デバッグ作業の効率化が図られます。
コミット
text/template/parse: don't show itemType in error messages
so that the user don't need to decipher something like this:
template: main:1: expected %!s(parse.itemType=14) in end; got "|"
now they get this:
template: main:1: unexpected "|" in end
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7128054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bee148bf23701f1b82d4d1d1187e3ef60ba724d7
元コミット内容
text/template/parse: don't show itemType in error messages
変更の背景
Go言語の text/template
パッケージは、テキストベースのテンプレートを解析(パース)し、実行するための機能を提供します。このパッケージは、WebアプリケーションのHTML生成や、設定ファイルの動的な生成など、様々な場面で利用されます。
しかし、テンプレートの記述に誤りがあった場合、パース時にエラーが発生します。このコミット以前は、エラーメッセージに parse.itemType=14
のような内部的な itemType
の情報が含まれていました。これは、テンプレートエンジンが内部的にトークンを識別するために使用する型情報であり、一般のユーザーや開発者にとっては理解しにくいものでした。
例えば、template: main:1: expected %!s(parse.itemType=14) in end; got "|"
のようなエラーメッセージは、itemType=14
が何を意味するのかを別途調べる必要があり、エラーの原因特定に余計な手間がかかっていました。この変更の背景には、このような不親切なエラーメッセージを改善し、ユーザーがより迅速かつ直感的にエラーを理解し、修正できるようにするという明確な目的があります。
前提知識の解説
Go言語の text/template
パッケージ
text/template
パッケージは、Go言語に組み込まれているテンプレートエンジンです。これは、プレースホルダーを含むテキストテンプレートを定義し、データ構造(Goの構造体、マップなど)をそのプレースホルダーにバインドして最終的なテキストを生成するために使用されます。
このパッケージは、主に以下の2つのフェーズで動作します。
- パース(Parse): テンプレート文字列を解析し、内部的な表現(構文木、
Tree
構造体)に変換します。この段階で、テンプレートの構文エラーがチェックされます。 - 実行(Execute): パースされたテンプレートと提供されたデータを結合し、最終的な出力を生成します。
text/template/parse
パッケージ
text/template/parse
パッケージは、text/template
パッケージの内部でテンプレートのパース処理を担当するサブパッケージです。このパッケージは、テンプレート文字列をトークンに分割し、それらのトークンを基に構文木を構築します。
itemType
itemType
は、text/template/parse
パッケージ内で定義されている列挙型(int
型)であり、テンプレートのパース中に識別される様々なトークンの種類を表します。例えば、itemEOF
(ファイルの終わり)、itemText
(通常のテキスト)、itemAction
({{...}}
で囲まれたアクション)、itemVariable
(変数)などがあります。
これらの itemType
は、パーサーがテンプレートの構文を解析し、期待されるトークンと実際に現れたトークンを比較するために内部的に使用されます。
エラーハンドリングと errorf
Go言語では、エラーは通常、error
インターフェースを実装する値として返されます。fmt.Errorf
関数は、フォーマットされた文字列から新しいエラーを生成するための一般的な方法です。text/template/parse
パッケージの内部では、Tree
構造体に関連付けられた errorf
メソッドが、パース中に発生したエラーを報告するために使用されていました。このメソッドは fmt.Errorf
と同様に、フォーマット文字列と引数を受け取り、エラーメッセージを生成します。
技術的詳細
このコミットの技術的な核心は、エラーメッセージの生成方法を変更し、内部的な itemType
の詳細をユーザーから隠蔽することにあります。
変更前は、Tree
構造体の expect
および expectOneOf
メソッド内で、期待される itemType
と実際に現れたトークンが一致しない場合に t.errorf
メソッドが呼び出されていました。この errorf
のフォーマット文字列には、%s
プレースホルダーが使用されており、itemType
の値が直接渡されていました。Goの fmt
パッケージは、%s
フォーマット指定子に対して、渡された値の String()
メソッド(もしあれば)を呼び出すか、そうでなければデフォルトの文字列表現を使用します。itemType
は int
型であり、そのデフォルトの文字列表現は parse.itemType=14
のような形式になっていました。これは、itemType
が String()
メソッドを実装していなかったため、その数値と型情報がそのまま出力されていたことを意味します。
変更後は、t.errorf
の代わりに t.unexpected
メソッドが呼び出されるようになりました。この t.unexpected
メソッドは、エラーメッセージを生成する際に itemType
の詳細を直接含めないように設計されています。代わりに、より汎用的な「予期しないトークン」というメッセージを生成し、ユーザーが理解しやすい形式でエラーを報告します。
具体的には、t.unexpected
メソッドは、token
オブジェクト(これは item
型であり、String()
メソッドを実装している)と context
文字列を受け取ります。item
型の String()
メソッドは、そのトークンの種類を人間が読みやすい文字列(例: "|"
、"end"
など)に変換するため、itemType
の内部的な数値表現がユーザーに表示されることはなくなります。
この変更により、エラーメッセージは template: main:1: unexpected "|" in end
のように、何が予期されず、どのコンテキストで発生したのかを明確に伝えるものとなり、デバッグの際に itemType
の数値と実際のトークンのマッピングを調べる手間がなくなります。
コアとなるコードの変更箇所
変更は src/pkg/text/template/parse/parse.go
ファイルにあります。
--- a/src/pkg/text/template/parse/parse.go
+++ b/src/pkg/text/template/parse/parse.go
@@ -151,7 +151,7 @@ func (t *Tree) error(err error) {
func (t *Tree) expect(expected itemType, context string) item {
token := t.nextNonSpace()
if token.typ != expected {
- t.errorf("expected %s in %s; got %s", expected, context, token)
+ t.unexpected(token, context)
}
return token
}
@@ -160,7 +160,7 @@ func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
token := t.nextNonSpace()
if token.typ != expected1 && token.typ != expected2 {
- t.errorf("expected %s or %s in %s; got %s", expected1, expected2, context, token)
+ t.unexpected(token, context)
}
return token
}
コアとなるコードの解説
このコミットでは、src/pkg/text/template/parse/parse.go
内の2つの関数、expect
と expectOneOf
のエラー報告ロジックが変更されています。
-
func (t *Tree) expect(expected itemType, context string) item
- この関数は、パーサーが特定の
itemType
のトークンを期待しているときに呼び出されます。 t.nextNonSpace()
で次の非空白トークンを取得します。if token.typ != expected
の条件で、取得したトークンの型が期待される型と異なる場合、エラーを報告します。- 変更前:
t.errorf("expected %s in %s; got %s", expected, context, token)
expected
(itemType) が%s
でフォーマットされるため、parse.itemType=XX
のような内部表現がそのまま出力されていました。token
(item) も%s
でフォーマットされ、そのString()
メソッドが呼び出されていました。
- 変更後:
t.unexpected(token, context)
t.errorf
の直接的な呼び出しがt.unexpected
メソッドの呼び出しに置き換えられました。t.unexpected
メソッドは、内部でよりユーザーフレンドリーなエラーメッセージを生成するように実装されています。これにより、itemType
の内部的な数値がエラーメッセージに表示されなくなります。
- この関数は、パーサーが特定の
-
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item
- この関数は、パーサーが2つのうちいずれかの
itemType
のトークンを期待しているときに呼び出されます。 - ロジックは
expect
関数と似ており、取得したトークンの型がexpected1
またはexpected2
のいずれとも一致しない場合にエラーを報告します。 - 変更前:
t.errorf("expected %s or %s in %s; got %s", expected1, expected2, context, token)
- ここでも
expected1
とexpected2
(itemType) が%s
でフォーマットされ、内部表現が出力されていました。
- ここでも
- 変更後:
t.unexpected(token, context)
- 同様に
t.unexpected
メソッドの呼び出しに置き換えられ、エラーメッセージからitemType
の内部表現が削除されました。
- 同様に
- この関数は、パーサーが2つのうちいずれかの
この変更は、エラーメッセージの可読性を向上させるための小さな、しかし重要な改善です。内部的な型情報をユーザーに公開する代わりに、より抽象的で理解しやすい「予期しないトークン」という表現を用いることで、開発者のデバッグ体験が向上します。
関連リンク
- Go CL 7128054: https://golang.org/cl/7128054
参考にした情報源リンク
- Go text/template package documentation: https://pkg.go.dev/text/template
- Go text/template/parse package documentation: https://pkg.go.dev/text/template/parse
- Go fmt package documentation: https://pkg.go.dev/fmt
- Go
String()
method for custom types: https://gobyexample.com/string-formatting (General concept ofString()
method for custom types in Go) - Go
error
interface: https://go.dev/blog/error-handling-and-go (General concept of error handling in Go)