[インデックス 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つの問題に対処するために行われました。
- 型チェッカーのエラー出力の抑制:
cmd/vet
は内部でGoの型チェッカーを利用していますが、型チェッカーが報告するエラーの中には、vet
が本来検出したい問題とは異なる、より低レベルな構文エラーや型エラーが含まれる場合があります。これらのエラーが常に表示されると、vet
の出力が冗長になり、本当に重要な警告が見過ごされる可能性がありました。この変更により、冗長モードが有効でない限り、型チェッカーからのエラーは抑制され、vet
の出力がより目的に特化したものになります。 _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
ファイルに集中しています。
-
prefixDirectory
関数の追加:prefixDirectory
は、与えられたディレクトリ名とファイル名のリストを受け取り、リスト内の各ファイル名にディレクトリ名をプレフィックスとして付加するヘルパー関数です。これは、build.Default.ImportDir
が返すファイル名がディレクトリ名を含まない場合に、完全なパスを構築するために使用されます。 -
doPackageDir
関数の変更:doPackageDir
関数は、指定されたディレクトリ内のGoパッケージを解析する役割を担っています。- 以前は
pkg.GoFiles
とpkg.CgoFiles
のみを対象としていましたが、この変更によりpkg.TestGoFiles
もnames
リストに追加されるようになりました。pkg.TestGoFiles
は、通常のパッケージと同じパッケージに属する_test.go
ファイルを指します。 - さらに、
pkg.XTestGoFiles
(外部テストパッケージのファイル)が存在する場合、それらも個別にdoPackage
関数に渡して解析するように変更されました。これにより、通常のGoファイル、Cgoファイル、内部テストファイル、外部テストファイルがすべてvet
の解析対象に含まれるようになります。 prefixDirectory
関数が導入され、ファイル名にディレクトリパスを適切に付加する処理が共通化されました。
- 以前は
-
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.GoFiles
と pkg.CgoFiles
のみを対象としていましたが、変更後は pkg.TestGoFiles
も names
スライスに追加され、通常の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言語の公式ドキュメント: https://go.dev/
go vet
のドキュメント: https://go.dev/cmd/vet/- Go言語のテストに関するドキュメント: https://go.dev/pkg/testing/
- Go言語のIssueトラッカー: https://github.com/golang/go/issues
参考にした情報源リンク
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トラッカーで過去に議論された可能性があります。