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

[インデックス 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つのフェーズで動作します。

  1. パース(Parse): テンプレート文字列を解析し、内部的な表現(構文木、Tree構造体)に変換します。この段階で、テンプレートの構文エラーがチェックされます。
  2. 実行(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() メソッド(もしあれば)を呼び出すか、そうでなければデフォルトの文字列表現を使用します。itemTypeint 型であり、そのデフォルトの文字列表現は parse.itemType=14 のような形式になっていました。これは、itemTypeString() メソッドを実装していなかったため、その数値と型情報がそのまま出力されていたことを意味します。

変更後は、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つの関数、expectexpectOneOf のエラー報告ロジックが変更されています。

  1. 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 の内部的な数値がエラーメッセージに表示されなくなります。
  2. 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)
      • ここでも expected1expected2 (itemType) が %s でフォーマットされ、内部表現が出力されていました。
    • 変更後: t.unexpected(token, context)
      • 同様に t.unexpected メソッドの呼び出しに置き換えられ、エラーメッセージから itemType の内部表現が削除されました。

この変更は、エラーメッセージの可読性を向上させるための小さな、しかし重要な改善です。内部的な型情報をユーザーに公開する代わりに、より抽象的で理解しやすい「予期しないトークン」という表現を用いることで、開発者のデバッグ体験が向上します。

関連リンク

参考にした情報源リンク