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

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

このコミットは、Go言語の静的解析ツールである cmd/vet におけるエラー出力の改善に関するものです。具体的には、エラーメッセージから列情報(カラム番号)を削除し、ファイル名と行番号のみを表示するように変更しています。これは、列情報が誤解を招く可能性があるという判断に基づいています。

コミット

commit 7f284f85f908909a498663d54da689e56cd38e73
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 13 22:34:37 2013 -0500

    cmd/vet: drop column information from error
    
    The column information can be misleading.
    
    R=golang-dev, dsymonds, r
    CC=golang-dev
    https://golang.org/cl/7300100

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

https://github.com/golang/go/commit/7f284f85f908909a498663d54da689e56cd38e73

元コミット内容

cmd/vet: drop column information from error The column information can be misleading.

このコミットは、cmd/vet ツールが生成するエラーメッセージから列情報を削除することを目的としています。その理由は、列情報が誤解を招く可能性があるためです。

変更の背景

cmd/vet はGo言語のコードを静的に解析し、潜在的なバグや疑わしいコード構造を報告するツールです。通常、エラーや警告を報告する際には、問題が発生したファイル名、行番号、そして列番号(カラム番号)を含めることで、開発者がコードの正確な位置を特定できるようにします。

しかし、このコミットの背景には、cmd/vet が報告するエラーの性質が関係しています。cmd/vet は、例えば fmt.Printf のような可変引数関数において、フォーマット文字列と引数の型が一致しないといった問題を検出します。このような場合、エラーが指し示す token.Pos (ソースコード上の位置) は、問題のある式全体の開始位置を指すことが多く、必ずしもエラーの根本原因となる特定の文字やトークンの位置を正確に指しているわけではありません。

コミットメッセージにある「The column information can be misleading. (列情報が誤解を招く可能性がある)」という記述は、この状況を指しています。エラーメッセージに列情報が含まれていると、開発者はその列番号が示すピンポイントな位置に問題があると誤解し、実際にはその周辺の式全体や、より広範なコード構造に問題があるにもかかわらず、誤った箇所に注意を向けてしまう可能性があります。

この誤解を避けるため、Goチームは cmd/vet のエラー出力から列情報を削除し、ファイル名と行番号のみに限定することで、開発者がより広い視野で問題箇所を検討するように促すことを決定しました。これにより、エラーメッセージの精度が向上し、開発者のデバッグ体験が改善されると考えられます。

前提知識の解説

cmd/vet

cmd/vet は、Go言語の標準ツールチェーンに含まれる静的解析ツールです。Goのソースコードを分析し、以下のような潜在的な問題や疑わしいコードパターンを検出します。

  • Printf フォーマット文字列の不一致: fmt.Printf や関連する関数で、フォーマット文字列と引数の型が一致しない場合。
  • 構造体タグの誤り: json:"..."xml:"..." のような構造体タグの構文エラー。
  • ロックの誤用: sync.Mutex などのロックが正しく使用されていない場合(例: ロックが解除されないまま関数が終了する)。
  • 未使用の変数やインポート: コンパイラが警告しないが、コードの品質を低下させる可能性のある未使用要素。
  • シャドーイング: 外部スコープの変数を内部スコープで同名の変数で隠蔽してしまう場合。

cmd/vet はコンパイラとは異なり、コードが文法的に正しいかどうかをチェックするのではなく、実行時に問題を引き起こす可能性のある「疑わしい」コードを特定することを目的としています。これにより、開発者は早期に潜在的なバグを発見し、コードの信頼性を向上させることができます。

go/token パッケージと token.Pos

Go言語のコンパイラやツールは、ソースコードを解析する際に go/token パッケージを利用します。このパッケージは、ソースコード内の位置情報を管理するための基本的な機能を提供します。

  • token.Pos: ソースコード内の特定の位置を表す抽象的な型です。これは単なる数値であり、それ自体ではファイル名、行番号、列番号といった具体的な情報を含んでいません。
  • token.FileSet: token.Pos を具体的なファイル名、行番号、列番号に変換するためのマッピングを管理する構造体です。ソースコードを解析する際には、まず token.FileSet を作成し、その中に解析対象のファイルを追加していきます。
  • FileSet.Position(pos token.Pos) メソッド: このメソッドは、与えられた token.Pos から、その位置に対応する token.Position 構造体を返します。token.Position 構造体には、Filename (ファイル名)、Line (行番号)、Column (列番号) といった具体的な位置情報が含まれています。
  • token.Position.String() メソッド: token.Position 構造体は、filename:line:column の形式で位置情報を文字列として返す String() メソッドを持っています。例えば、/path/to/file.go:10:5 のようになります。

このコミットでは、この token.Position.String() メソッドが生成する文字列から列情報が削除されるように、cmd/vet のエラー報告ロジックが変更されています。

技術的詳細

このコミットの技術的な変更は、src/cmd/vet/main.go ファイル内のエラー報告関数に集中しています。

以前の Warn および Warnf 関数では、f.fset.Position(pos).String() を直接呼び出して位置情報を取得し、それをエラーメッセージのプレフィックスとして使用していました。この String() メソッドは、デフォルトでファイル名、行番号、列番号を含む文字列を生成します。

変更後、新しいヘルパー関数 loc(pos token.Pos) が導入されました。この loc 関数は以下の処理を行います。

  1. f.fset.Position(pos) を呼び出して、token.Pos から具体的な token.Position 構造体 (posn) を取得します。
  2. fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line) を使用して、ファイル名と行番号のみを含む文字列を生成します。ここで、posn.Column は意図的に無視されます。
  3. この生成された文字列を返します。

そして、Warn および Warnf 関数は、直接 f.fset.Position(pos).String() を呼び出す代わりに、新しく導入された f.loc(pos) 関数を呼び出すように変更されました。これにより、cmd/vet が出力するすべてのエラーメッセージから列情報が効果的に削除されます。

この変更は、エラーメッセージのフォーマットを中央集権的に管理し、将来的に同様の変更が必要になった場合でも、loc 関数を修正するだけで済むようにするという、コードの保守性の観点からも良いプラクティスと言えます。

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

変更は src/cmd/vet/main.go ファイルにあります。

--- a/src/cmd/vet/main.go
+++ b/src/cmd/vet/main.go
@@ -195,16 +195,22 @@ func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
 	setExit(1)
 }
 
+func (f *File) loc(pos token.Pos) string {
+	// Do not print columns. Because the pos often points to the start of an
+	// expression instead of the inner part with the actual error, the
+	// precision can mislead.
+	posn := f.fset.Position(pos)
+	return fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line)
+}
+
 // Warn reports an error but does not set the exit code.
 func (f *File) Warn(pos token.Pos, args ...interface{}) {
-	loc := f.fset.Position(pos).String() + ": "
-	fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...))
+	fmt.Fprint(os.Stderr, f.loc(pos)+fmt.Sprintln(args...))
 }
 
 // Warnf reports a formatted error but does not set the exit code.
 func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
-	loc := f.fset.Position(pos).String() + ": "
-	fmt.Fprintf(os.Stderr, loc+format+"\n", args...)
+	fmt.Fprintf(os.Stderr, f.loc(pos)+format+"\n", args...)
 }
 
 // walkFile walks the file's tree.

コアとなるコードの解説

追加されたコード

func (f *File) loc(pos token.Pos) string {
	// Do not print columns. Because the pos often points to the start of an
	// expression instead of the inner part with the actual error, the
	// precision can mislead.
	posn := f.fset.Position(pos)
	return fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line)
}

この新しいメソッド loc は、*File 型(cmd/vet が解析中のファイルを表現する内部構造体)に紐付けられています。

  • 引数として token.Pos を受け取ります。これは、エラーが発生したソースコード上の抽象的な位置です。
  • f.fset.Position(pos) を呼び出すことで、抽象的な token.Pos を具体的なファイル名、行番号、列番号を含む token.Position 構造体 (posn) に変換します。f.fset は、*File 構造体が持つ token.FileSet への参照です。
  • fmt.Sprintf("%s:%d: ", posn.Filename, posn.Line) を使用して、posn.Filename (ファイル名) と posn.Line (行番号) のみを抽出し、"ファイル名:行番号: " の形式の文字列を生成して返します。
  • コメント // Do not print columns. Because the pos often points to the start of an // expression instead of the inner part with the actual error, the // precision can mislead. は、この変更の意図を明確に示しています。エラー位置が式の開始点を指すことが多く、列情報が誤解を招く可能性があるため、列情報を出力しないという方針です。

変更されたコード

// Warn reports an error but does not set the exit code.
func (f *File) Warn(pos token.Pos, args ...interface{}) {
-	loc := f.fset.Position(pos).String() + ": "
-	fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...))
+	fmt.Fprint(os.Stderr, f.loc(pos)+fmt.Sprintln(args...))
}

// Warnf reports a formatted error but does not set the exit code.
func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
-	loc := f.fset.Position(pos).String() + ": "
-	fmt.Fprintf(os.Stderr, loc+format+"\n", args...)
+	fmt.Fprintf(os.Stderr, f.loc(pos)+format+"\n", args...)
}

WarnWarnf は、cmd/vet が警告メッセージを標準エラー出力に書き出すための関数です。

  • 変更前は、f.fset.Position(pos).String() + ": " を直接使用して位置情報文字列を生成していました。token.Position.String() メソッドは、デフォルトで列情報を含む filename:line:column 形式の文字列を返します。
  • 変更後は、新しく追加された f.loc(pos) メソッドを呼び出すように修正されました。これにより、Warn および Warnf が出力するすべての警告メッセージにおいて、列情報が自動的に削除され、ファイル名と行番号のみが表示されるようになります。

この変更により、cmd/vet のエラー出力はより簡潔になり、開発者がエラーの根本原因を特定する際に、誤解を招く可能性のある詳細情報に惑わされることなく、より本質的な問題に集中できるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Gerrit Code Review (golang.org/cl)
  • Stack Overflow や技術ブログなど、Go言語の cmd/vetgo/token に関する一般的な情報源。