[インデックス 13711] ファイルの概要
このコミットは、Go言語の標準ライブラリであるgo/scanner
パッケージ内のエラー出力に関するバグ修正です。具体的には、エラーが存在しない場合にPrintError
関数が不要な「ゴミ」を出力してしまう問題を解決しています。
コミット
commit d6c69dc602431828c5e5818b24bbf0593652480c
Author: Robert Griesemer <gri@golang.org>
Date: Thu Aug 30 16:10:33 2012 -0700
go/scanner: don't print garbage if there's no error
R=r
CC=golang-dev
https://golang.org/cl/6489059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d6c69dc602431828c5e5818b24bbf0593652480c
元コミット内容
このコミットは、go/scanner
パッケージのerrors.go
ファイルにあるPrintError
関数において、エラーオブジェクトがnil
であるにもかかわらず、else
ブロックが実行され、結果として不必要な出力("garbage")が行われる可能性があった問題を修正しています。
変更の背景
go/scanner
パッケージは、Goソースコードの字句解析(スキャン)を行うための機能を提供します。このパッケージには、解析中に発生したエラーを収集し、整形して出力するためのPrintError
関数が含まれています。
変更前のPrintError
関数は、引数として渡されたerr
がscanner.ErrorList
型であるかどうかをチェックし、もしそうであればそのリスト内の全てのエラーを出力していました。しかし、err
がErrorList
型ではない、かつnil
である場合(つまり、エラーが全く発生していない場合)、else
ブロックが実行されてしまい、fmt.Fprintf(w, "%s\n", err)
という行が実行されていました。
Go言語において、nil
のerror
インターフェース値を%s
フォーマット指定子で出力しようとすると、通常は<nil>
という文字列が出力されます。このコミットの背景にある問題は、エラーがないにもかかわらず、この<nil>
という文字列や、それに類する「ゴミ」が標準出力や指定されたio.Writer
に書き込まれてしまうことでした。これは、エラーがないことを示すために何も出力すべきではないという期待に反する動作であり、ログのノイズになったり、後続の処理に影響を与えたりする可能性がありました。
このコミットは、このような不必要な出力を防ぎ、PrintError
関数が真にエラーが存在する場合にのみ出力を行うようにするための修正です。
前提知識の解説
-
go/scanner
パッケージ: Go言語の標準ライブラリの一部で、Goソースコードの字句解析(lexical analysis)を行うための機能を提供します。ソースコードをトークン(識別子、キーワード、演算子など)のストリームに変換する役割を担います。このパッケージは、コンパイラやリンター、コードフォーマッターなど、Goコードを扱う様々なツールで利用されます。 -
error
インターフェース: Go言語におけるエラーハンドリングの基本的な仕組みです。error
は組み込みのインターフェースであり、Error() string
というメソッドを一つだけ持ちます。このメソッドは、エラーの文字列表現を返します。関数がエラーを返す場合、通常はerror
型の値を返します。エラーがない場合はnil
を返します。 -
scanner.ErrorList
型:go/scanner
パッケージ内で定義されている型で、複数のスキャンエラーをまとめるためのリストです。このErrorList
型もerror
インターフェースを実装しており、Error()
メソッドを呼び出すと、リスト内の全てのエラーメッセージを結合した文字列を返します。 -
io.Writer
インターフェース: Go言語におけるI/O操作の基本的なインターフェースの一つです。Write([]byte) (n int, err error)
メソッドを持ち、バイトスライスを書き込む機能を提供します。os.Stdout
(標準出力)、bytes.Buffer
、ファイルなど、様々な出力先がこのインターフェースを実装しています。 -
fmt.Fprintf
関数:fmt
パッケージの関数で、指定されたio.Writer
にフォーマットされた文字列を書き込みます。fmt.Fprintf(w, format, a...)
のように使用し、w
にio.Writer
、format
にフォーマット文字列、a
に可変引数を取ります。%s
は文字列をフォーマットするための動詞です。 -
型アサーション (
value.(type)
): Go言語の機能で、インターフェース型の変数が特定の具象型であるかどうかをチェックし、もしそうであればその具象型の値を取り出すことができます。value, ok := err.(ErrorList)
のように使用し、ok
は型アサーションが成功したかどうかを示す真偽値です。
技術的詳細
src/pkg/go/scanner/errors.go
ファイル内のPrintError
関数は、以下のような構造を持っていました(変更前):
func PrintError(w io.Writer, err error) {
if list, ok := err.(ErrorList); ok {
// errがErrorList型の場合、リスト内の各エラーを出力
for _, e := range list {
fmt.Fprintf(w, "%s\n", e)
}
} else {
// errがErrorList型ではない場合、err自体を出力
fmt.Fprintf(w, "%s\n", err)
}
}
このロジックの問題点は、err
がnil
の場合に発生します。
err
がnil
の場合、if list, ok := err.(ErrorList); ok
の型アサーションは失敗します。なぜなら、nil
はErrorList
型ではないからです。- 型アサーションが失敗すると、
ok
はfalse
となり、実行はelse
ブロックに移ります。 else
ブロック内でfmt.Fprintf(w, "%s\n", err)
が実行されます。このとき、err
はnil
であるため、fmt
パッケージはnil
のerror
インターフェース値を文字列としてフォーマットしようとします。結果として、w
には通常<nil>\n
という文字列が書き込まれます。
この<nil>
という出力は、エラーがないにもかかわらず出力されるため、「ゴミ」と表現されていました。
今回の修正は、このelse
ブロックの条件をより厳密にすることで、err
がnil
の場合にこのブロックが実行されないようにします。
変更後のロジックは以下のようになります:
func PrintError(w io.Writer, err error) {
if list, ok := err.(ErrorList); ok {
// errがErrorList型の場合、リスト内の各エラーを出力
for _, e := range list {
fmt.Fprintf(w, "%s\n", e)
}
} else if err != nil { // ここが変更点
// errがErrorList型ではない、かつerrがnilではない場合のみ、err自体を出力
fmt.Fprintf(w, "%s\n", err)
}
}
else if err != nil
という条件が追加されたことで、err
がErrorList
型ではない場合に加えて、err
がnil
ではないという条件も満たされた場合にのみ、fmt.Fprintf(w, "%s\n", err)
が実行されるようになります。これにより、err
がnil
の場合にはどのブロックも実行されなくなり、何も出力されないという期待通りの動作が実現されます。
この修正は、Go言語におけるエラーハンドリングの慣習、すなわち「エラーがない場合はnil
を返し、その際には何も出力しない」という原則に合致させるためのものです。
コアとなるコードの変更箇所
変更はsrc/pkg/go/scanner/errors.go
ファイル内のPrintError
関数の一箇所のみです。
--- a/src/pkg/go/scanner/errors.go
+++ b/src/pkg/go/scanner/errors.go
@@ -120,7 +120,7 @@ func PrintError(w io.Writer, err error) {
for _, e := range list {
fmt.Fprintf(w, "%s\n", e)
}
- } else {
+ } else if err != nil {
fmt.Fprintf(w, "%s\n", err)
}\n
}
具体的には、123行目の}
の直後にあるelse {
がelse if err != nil {
に変更されています。
コアとなるコードの解説
この変更は、PrintError
関数がエラーを整形して出力する際の条件をより厳密にすることで、不必要な出力を防ぐことを目的としています。
-
変更前:
else { ... }
err
がscanner.ErrorList
型でなかった場合、無条件にこのブロックが実行されていました。これにはerr
がnil
であるケースも含まれていました。nil
のerror
を%s
でフォーマットすると、<nil>
という文字列が出力されてしまい、エラーがないにもかかわらず出力が行われるという問題がありました。 -
変更後:
else if err != nil { ... }
else
にif err != nil
という条件が追加されました。これにより、err
がscanner.ErrorList
型ではない、かつerr
がnil
ではない場合にのみ、このブロックが実行されるようになります。 もしerr
がnil
であれば、最初のif
条件(err
がErrorList
型かどうかのチェック)も、このelse if
条件(err
がnil
ではないかどうかのチェック)も満たさないため、PrintError
関数は何も出力せずに終了します。
この修正により、PrintError
関数は、実際にエラーオブジェクトが存在し、かつそれがErrorList
型ではない単一のエラーである場合にのみ、そのエラーメッセージを出力するようになります。エラーが全くない(err
がnil
)場合には、何も出力されないという、よりクリーンで期待通りの動作が実現されました。
関連リンク
- Go言語の
go/scanner
パッケージのドキュメント: https://pkg.go.dev/go/scanner - Go言語の
error
インターフェースに関する公式ドキュメント: https://pkg.go.dev/builtin#error - Go言語の
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt - Go言語におけるエラーハンドリングの慣習("Error handling and Go" - Go公式ブログ): https://go.dev/blog/error-handling-and-go
参考にした情報源リンク
- このコミットのGerrit Change-ID:
https://golang.org/cl/6489059
(コミットメッセージに記載されているリンク) - Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語の公式ドキュメント全般
- Go言語のエラーハンドリングに関する一般的な知識