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

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

このコミットは、Go言語の静的解析ツールである cmd/vet の挙動を改善するものです。具体的には、型チェッカーからのエラー出力を冗長モード(verbose)が設定されていない限り抑制し、また、以前の「パッケージ単位の変更」によって失われていた _test.go ファイルのチェック機能を復元します。これにより、go vet の使い勝手と網羅性が向上しています。

コミット

  • コミットハッシュ: 78b3ef261d3ea28aa387be464c8d2c9b1618290c
  • Author: Rob Pike r@golang.org
  • Date: Mon Feb 25 16:29:09 2013 -0800

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

https://github.com/golang/go/commit/78b3ef261d3ea28aa387be464c8d2c9b1618290c

元コミット内容

    cmd/vet: silence error from type checker unless verbose is set.
    Also restores the checking of _test.go files, which disappeared
    as a result of the package-at-a-time change.
    Fixes #4895.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7399051

変更の背景

このコミットは、主に以下の2つの問題に対処するために行われました。

  1. 型チェッカーのエラー出力の抑制: cmd/vet は内部でGoの型チェッカーを利用していますが、型チェッカーが報告するエラーの中には、vet が本来検出したい問題とは異なる、より低レベルな構文エラーや型エラーが含まれる場合があります。これらのエラーが常に表示されると、vet の出力が冗長になり、本当に重要な警告が見過ごされる可能性がありました。この変更により、冗長モードが有効でない限り、型チェッカーからのエラーは抑制され、vet の出力がより目的に特化したものになります。
  2. _test.go ファイルのチェック機能の復元: 以前の go vet の実装では、パッケージ内のすべてのGoソースファイルを対象としていました。しかし、Goのビルドシステムにおける「パッケージ単位の変更 (package-at-a-time change)」という最適化の導入により、_test.go ファイルが通常のパッケージビルドプロセスから除外されるようになりました。これにより、go vet_test.go ファイルを適切に解析しなくなり、テストコード内の潜在的な問題を検出できなくなるという問題が発生しました。このコミットは、_test.go ファイル(および _xtest.go ファイル)も vet の解析対象に含めることで、この機能の欠落を修正しています。

これらの変更は、GoのIssue #4895 に関連しています。

前提知識の解説

go vet コマンド

go vet は、Go言語の標準ツールチェーンに含まれる静的解析ツールです。コンパイラでは検出できないが、潜在的なバグや疑わしいコード構造を特定し、報告することを目的としています。例えば、Printf フォーマット文字列と引数の不一致、到達不能なコード、無意味な代入、sync/atomic パッケージの誤用などを検出できます。go vet はヒューリスティックに基づいており、報告されたすべての問題が必ずしも実際のバグであるとは限りませんが、コード品質の向上と早期のバグ検出に役立ちます。

Goの型チェッカー

Goは静的型付け言語であり、変数の型はコンパイル時にチェックされます。Goコンパイラは基本的な型チェックを行いますが、より高度な型チェックや静的解析のために、go/types パッケージが提供されています。このパッケージは、Goパッケージの型チェックのためのデータ型とアルゴリズムを提供し、go vet のようなツールが内部で利用しています。

_test.go ファイル

Go言語では、ファイル名が _test.go で終わるファイルは、テストコードを含む特別なファイルとして扱われます。これらのファイルは通常のパッケージビルドには含まれず、go test コマンドが実行されたときにのみコンパイルおよび実行されます。テスト関数は Test で始まり、*testing.T 型の引数を取ります。また、XTestGoFiles は、パッケージとは別のテストパッケージ(例: mypackage_test)に属するテストファイルを指し、エクスポートされた識別子のみをテストする「ブラックボックステスト」に使用されます。

「パッケージ単位の変更 (package-at-a-time change)」

Goのビルドシステムは、効率的なコンパイルのために「パッケージ単位」で動作します。これは、ソースファイルが変更された場合、そのファイルが属するパッケージと、そのパッケージに依存する他のパッケージのみを再コンパイルするという最適化戦略を指します。これにより、プロジェクト全体を毎回再ビルドすることなく、高速なインクリメンタルビルドが可能になります。この最適化の過程で、_test.go ファイルが通常のビルドパスから一時的に外れることがあり、それが go vet の問題を引き起こしました。

技術的詳細

このコミットの主要な変更は、src/cmd/vet/main.go ファイルに集中しています。

  1. prefixDirectory 関数の追加: prefixDirectory は、与えられたディレクトリ名とファイル名のリストを受け取り、リスト内の各ファイル名にディレクトリ名をプレフィックスとして付加するヘルパー関数です。これは、build.Default.ImportDir が返すファイル名がディレクトリ名を含まない場合に、完全なパスを構築するために使用されます。

  2. doPackageDir 関数の変更: doPackageDir 関数は、指定されたディレクトリ内のGoパッケージを解析する役割を担っています。

    • 以前は pkg.GoFilespkg.CgoFiles のみを対象としていましたが、この変更により pkg.TestGoFilesnames リストに追加されるようになりました。pkg.TestGoFiles は、通常のパッケージと同じパッケージに属する _test.go ファイルを指します。
    • さらに、pkg.XTestGoFiles(外部テストパッケージのファイル)が存在する場合、それらも個別に doPackage 関数に渡して解析するように変更されました。これにより、通常のGoファイル、Cgoファイル、内部テストファイル、外部テストファイルがすべて vet の解析対象に含まれるようになります。
    • prefixDirectory 関数が導入され、ファイル名にディレクトリパスを適切に付加する処理が共通化されました。
  3. doPackage 関数の変更: doPackage 関数は、実際にGoの型チェッカー (context.Check) を呼び出してパッケージの型チェックを行う部分です。

    • 型チェッカーからのエラー処理が変更されました。以前は if err != nil で常にエラーを報告していましたが、変更後は if err != nil && *verbose となり、verbose フラグが設定されている場合にのみエラーが報告されるようになりました。これにより、デフォルトでは型チェッカーの低レベルなエラーが抑制され、vet の出力がより簡潔になります。

これらの変更により、go vet はより包括的にGoコードを解析し、同時に不要なエラーメッセージを抑制することで、ユーザーエクスペリエンスを向上させています。

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

--- a/src/cmd/vet/main.go
+++ b/src/cmd/vet/main.go
@@ -137,7 +137,17 @@ func main() {
 	os.Exit(exitCode)
 }
 
-// doPackageDir analyzes the single package found in the directory, if there is one.
+// prefixDirectory places the directory name on the beginning of each name in the list.
+func prefixDirectory(directory string, names []string) {
+	if directory != "." {
+		for i, name := range names {
+			names[i] = filepath.Join(directory, name)
+		}
+	}
+}
+
+// doPackageDir analyzes the single package found in the directory, if there is one,
+// plus a test package, if there is one.
 func doPackageDir(directory string) {
 	pkg, err := build.Default.ImportDir(directory, 0)
 	if err != nil {
@@ -149,14 +159,17 @@ func doPackageDir(directory string) {
 		warnf("cannot process directory %s: %s", directory, err)
 		return
 	}
-	names := append(pkg.GoFiles, pkg.CgoFiles...)
-	// Prefix file names with directory names.
-	if directory != "." {
-		for i, name := range names {
-			names[i] = filepath.Join(directory, name)
-		}
-	}
+	var names []string
+	names = append(names, pkg.CgoFiles...)
+	names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
+	prefixDirectory(directory, names)
 	doPackage(names)
+	// Is there also a "foo_test" package? If so, do that one as well.
+	if len(pkg.XTestGoFiles) > 0 {
+		names = pkg.XTestGoFiles
+		prefixDirectory(directory, names)
+		doPackage(names)
+	}
 }
 
 type Package struct {
@@ -201,7 +214,7 @@ func doPackage(names []string) {
 	}
 	// Type check the package.
 	_, err := context.Check(fs, astFiles)
-	if err != nil {\n+\tif err != nil && *verbose {\n \t\twarnf(\"%s\", err)\n \t}\n \tfor _, file := range files {\n```

## コアとなるコードの解説

### `prefixDirectory` 関数

```go
func prefixDirectory(directory string, names []string) {
	if directory != "." {
		for i, name := range names {
			names[i] = filepath.Join(directory, name)
		}
	}
}

この関数は、ファイルパスのリスト names の各要素に、指定された directory をプレフィックスとして付加します。filepath.Join を使用することで、OSに依存しないパス結合が保証されます。directory != "." のチェックは、カレントディレクトリの場合に不要なパス結合を避けるための最適化です。

doPackageDir 関数の変更点

func doPackageDir(directory string) {
	// ... (既存のコード) ...

	var names []string
	names = append(names, pkg.CgoFiles...)
	names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
	prefixDirectory(directory, names)
	doPackage(names)

	// Is there also a "foo_test" package? If so, do that one as well.
	if len(pkg.XTestGoFiles) > 0 {
		names = pkg.XTestGoFiles
		prefixDirectory(directory, names)
		doPackage(names)
	}
}

変更前は pkg.GoFilespkg.CgoFiles のみを対象としていましたが、変更後は pkg.TestGoFilesnames スライスに追加され、通常のGoファイルやCgoファイルと共に doPackage に渡されます。これにより、同じパッケージに属する _test.go ファイルも vet の解析対象となります。

さらに、pkg.XTestGoFiles の存在チェックが追加されました。これは、package foo_test のように、メインパッケージとは別の名前空間で定義されるテストファイル(外部テスト)を指します。これらのファイルが存在する場合、それらも prefixDirectory でパスを調整した後、独立して doPackage に渡され、解析されます。これにより、go vet はパッケージ内のすべての種類のGoソースファイル(通常のコード、Cgoコード、内部テスト、外部テスト)を網羅的にチェックできるようになりました。

doPackage 関数の変更点

func doPackage(names []string) {
	// ... (既存のコード) ...

	// Type check the package.
	_, err := context.Check(fs, astFiles)
	if err != nil && *verbose { // 変更点
		warnf("%s", err)
	}
	// ... (既存のコード) ...
}

この変更は、型チェッカー (context.Check) から返されるエラーの報告方法を制御します。以前は err != nil で常にエラーを報告していましたが、&& *verbose が追加されたことで、verbose フラグがコマンドラインで指定されている場合にのみエラーが warnf (警告として出力) されるようになりました。これにより、デフォルトの実行では、go vet が検出したい特定のパターンに焦点を当て、型チェッカーからの一般的なエラーメッセージは抑制され、出力がよりクリーンになります。

関連リンク

参考にした情報源リンク

  • go vet の概要: https://dev.to/ (具体的な記事URLは検索結果から特定できませんでした)
  • Goの型チェックについて: https://dolthub.com/ (具体的な記事URLは検索結果から特定できませんでした)
  • _test.go ファイルについて: https://go.dev/blog/testing
  • Goのビルドプロセスとパッケージ: https://ieftimov.com/ (具体的な記事URLは検索結果から特定できませんでした)
  • GoのIssue #4895: 直接的な情報は見つかりませんでしたが、コミットメッセージに記載されているため、Goの公式Issueトラッカーで過去に議論された可能性があります。