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

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

このコミットは、Go言語の標準ライブラリであるtext/templateおよびhtml/templateパッケージにおけるテンプレート実行時のエラー処理に関するドキュメントの明確化を目的としています。具体的には、テンプレートの実行中または出力の書き込み中にエラーが発生した場合でも、部分的な結果が既に書き込み先に書き込まれている可能性があるという重要な動作について、公式ドキュメントに追記しています。

コミット

commit 431b96bdbe7dc838551efc9959b3bdca780b9368
Author: Rob Pike <r@golang.org>
Date:   Mon May 19 14:29:45 2014 -0700

    text/template,html/template: document that partial results may be written on error
    Fixes #7445.
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/94640043

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

https://github.com/golang/go/commit/431b96bdbe7dc838551efc9959b3bdca780b9368

元コミット内容

text/template,html/template: document that partial results may be written on error
Fixes #7445.

変更の背景

このコミットの背景には、Goのテンプレートパッケージ(text/templatehtml/template)のExecuteおよびExecuteTemplateメソッドがエラーを返した場合の出力の挙動に関する潜在的な誤解があったと考えられます。通常、関数がエラーを返すと、その操作が完全に失敗し、副作用がないと期待されることがあります。しかし、テンプレートの実行はストリームベースで行われるため、エラーが発生するまでに一部の出力が既にio.Writerに書き込まれている可能性があります。

この挙動は、特にウェブアプリケーションなどでテンプレートを使用してHTTPレスポンスを生成する際に重要です。もしテンプレートの実行中にエラーが発生し、その時点で既にHTTPヘッダーが送信され、部分的なHTMLコンテンツがクライアントに送信されていた場合、アプリケーションはエラーページを完全に表示することができません。

このコミットは、このような「部分的な結果が書き込まれる可能性がある」という動作を明示的にドキュメントに追記することで、開発者がこの挙動を正しく理解し、適切なエラーハンドリングや出力の取り扱いを実装できるようにすることを目的としています。Fixes #7445という記述がありますが、このイシューの具体的な内容は公開されていません。しかし、このコミットメッセージから、テンプレートの出力に関する挙動の明確化が求められていたことが推測されます。

前提知識の解説

Go言語のtext/templateおよびhtml/templateパッケージ

Go言語には、テキストベースの出力やHTML出力を生成するための強力なテンプレートエンジンが標準ライブラリとして提供されています。

  • text/template: 任意のテキスト形式の出力を生成するために使用されます。設定ファイル、コード生成、プレーンテキストのレポートなどに適しています。
  • html/template: text/templateをベースにしており、HTML出力に特化しています。クロスサイトスクリプティング(XSS)攻撃を防ぐために、自動的にコンテキストに応じたエスケープ処理を行います。ウェブアプリケーションでHTMLページを動的に生成する際に不可欠です。

これらのパッケージは、テンプレート文字列を解析(Parse)し、データ構造(通常はGoの構造体やマップ)を適用(Execute)して最終的な出力を生成します。

テンプレートの実行とio.Writer

テンプレートのExecuteおよびExecuteTemplateメソッドは、第一引数にio.Writerインターフェースを受け取ります。io.Writerは、バイトストリームを書き込むための汎用的なインターフェースであり、ファイル、ネットワーク接続、標準出力など、様々な出力先にデータを書き込むことができます。

テンプレートエンジンは、テンプレートを解析し、データと結合しながら、結果をこのio.Writerに逐次的に書き込んでいきます。これは、テンプレート全体がメモリに構築されてから一度に書き込まれるのではなく、処理が進むにつれて少しずつ出力されることを意味します。

Go言語のエラーハンドリング

Go言語では、エラーは戻り値として明示的に扱われます。関数がエラーを返す場合、通常は最後の戻り値としてerror型の値が返されます。nilはエラーがないことを意味し、非nilの値はエラーが発生したことを示します。

このコミットが強調しているのは、Goのエラーハンドリングの一般的な慣習(エラーが返されたら操作は完全に失敗し、副作用はない)が、ストリームベースのI/O操作においては必ずしも当てはまらないという点です。

技術的詳細

このコミットは、Goのtext/templateおよびhtml/templateパッケージのExecuteおよびExecuteTemplateメソッドのドキュメントに、以下の文言を追加しています。

If an error occurs executing the template or writing its output, execution stops, but partial results may already have been written to the output writer.

この変更は、コードの動作自体を変更するものではなく、その動作に関する公式な説明を明確にするものです。これは、テンプレートエンジンの内部的な動作、特にio.Writerへの逐次的な書き込みという特性に起因する挙動を明示しています。

なぜこのドキュメントの追加が重要なのか?

  1. 誤解の解消: 多くの開発者は、関数がエラーを返した場合、その関数が何も変更を加えなかったと仮定しがちです。しかし、テンプレートの実行においては、エラーが発生するまでに一部のデータが既にio.Writerに書き込まれている可能性があります。このドキュメントは、この誤解を解消します。
  2. 堅牢なアプリケーションの構築: この挙動を理解することで、開発者はより堅牢なアプリケーションを構築できます。例えば、ウェブアプリケーションでテンプレートの実行中にエラーが発生した場合、既に部分的なHTMLがクライアントに送信されている可能性があるため、エラーページを完全に表示できないかもしれません。開発者は、このようなシナリオを考慮して、エラー発生時のリカバリ戦略(例: エラーが発生した場合は、既に書き込まれた部分を破棄し、適切なエラーメッセージを送信する)を設計する必要があります。
  3. 予測可能な動作: ドキュメントが明確であることで、開発者はテンプレートエンジンの動作をより正確に予測し、それに基づいてコードを記述することができます。

この変更は、Goの「明確さ」と「実用性」という設計哲学に沿ったものです。潜在的な混乱の源を特定し、それをドキュメントで明確にすることで、開発者の生産性とコードの品質向上に貢献しています。

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

このコミットでは、以下の2つのファイルにドキュメントの追記が行われています。

src/pkg/html/template/template.go

--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -62,6 +62,9 @@ func (t *Template) escape() error {
 
 // Execute applies a parsed template to the specified data object,
 // writing the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
 // A template may be executed safely in parallel.
 func (t *Template) Execute(wr io.Writer, data interface{}) error {
  if err := t.escape(); err != nil {
@@ -72,6 +75,9 @@ func (t *Template) Execute(wr io.Writer, data interface{}) error {
 
 // ExecuteTemplate applies the template associated with t that has the given
 // name to the specified data object and writes the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
 // A template may be executed safely in parallel.
 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
  tmpl, err := t.lookupAndEscapeTemplate(name)

src/pkg/text/template/exec.go

--- a/src/pkg/text/template/exec.go
+++ b/src/pkg/text/template/exec.go
@@ -108,6 +108,9 @@ func errRecover(errp *error) {
 
 // ExecuteTemplate applies the template associated with t that has the given name
 // to the specified data object and writes the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
 // A template may be executed safely in parallel.
 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
  tmpl := t.tmpl[name]
@@ -119,6 +122,9 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})\
 
 // Execute applies a parsed template to the specified data object,
 // and writes the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
 // A template may be executed safely in parallel.
 func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
  defer errRecover(&err)

コアとなるコードの解説

上記の変更箇所は、html/templateパッケージのTemplate.ExecuteおよびTemplate.ExecuteTemplateメソッド、そしてtext/templateパッケージのTemplate.ExecuteおよびTemplate.ExecuteTemplateメソッドのドキュメントコメントに、全く同じ新しい行を追加しています。

追加された行は以下の通りです。

// If an error occurs executing the template or writing its output,
// execution stops, but partial results may already have been written to
// the output writer.

このコメントは、これらのメソッドの動作に関する重要な注意点を明確にしています。

  • If an error occurs executing the template or writing its output, execution stops,: これは、テンプレートの処理中にエラーが発生した場合、それ以上の処理は行われず、メソッドの実行が停止することを意味します。これは一般的なエラーハンドリングの挙動と一致します。
  • but partial results may already have been written to the output writer.: これがこのコミットの核心です。テンプレートエンジンは出力を逐次的にio.Writerに書き込むため、エラーが発生する前に既に一部のデータが書き込まれている可能性があることを明示しています。つまり、エラーが返されたからといって、io.Writerが全く変更されていないと仮定してはならない、ということです。

このドキュメントの追加により、開発者はテンプレートの実行が失敗した場合でも、出力ストリームの状態を適切に考慮する必要があることを明確に理解できます。これにより、エラー発生時のアプリケーションの挙動がより予測可能になり、堅牢なエラーハンドリングロジックの実装が促されます。

関連リンク

参考にした情報源リンク

  • Go言語公式ドキュメント
  • Go言語のテンプレートパッケージに関する一般的な知識
  • io.Writerインターフェースに関するGo言語のドキュメント
  • Go言語のエラーハンドリングに関する一般的な慣習