[インデックス 12451] ファイルの概要
このコミットは、Go言語の標準ライブラリおよびツールにおけるタイプミス(typo)を修正するものです。具体的には、log.Fatal
および t.Fatal
の呼び出しを、フォーマット文字列をサポートする log.Fatalf
および t.Fatalf
に変更しています。これにより、エラーメッセージの出力がより柔軟になり、一貫性が向上します。影響を受けるファイルは src/cmd/go/build.go
、src/cmd/godoc/godoc.go
、src/pkg/net/unicast_test.go
の3つです。
コミット
commit 881966d2a5ff74df442017097a849645b5112682
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Mar 7 12:41:43 2012 +0800
cmd/go, cmd/godoc, net: fix typo
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5757050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/881966d2a5ff74df442017097a849645b5112682
元コミット内容
cmd/go, cmd/godoc, net: fix typo
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5757050
変更の背景
このコミットの背景には、Go言語の標準ライブラリやツール内で使用されているエラーロギングおよびテスト失敗報告の関数呼び出しにおける一貫性の欠如と、潜在的な機能不足がありました。
log.Fatal
と log.Fatalf
、そして testing.T.Fatal
と testing.T.Fatalf
は、いずれもプログラムの実行を停止させる(またはテストを失敗させる)点で共通していますが、メッセージのフォーマット方法に違いがあります。
log.Fatal(v ...any)
やt.Fatal(args ...interface{})
は、引数をそのまま出力します。log.Fatalf(format string, v ...any)
やt.Fatalf(format string, args ...interface{})
は、fmt.Printf
と同様にフォーマット文字列と可変引数を受け取り、整形されたメッセージを出力できます。
元のコードでは、フォーマット文字列を使用すべき箇所で Fatal
が誤って使用されている、あるいは Fatalf
を使用することでより明確なエラーメッセージを提供できるにもかかわらず Fatal
が使われている、という状況がありました。これは「typo」(タイプミス)と表現されていますが、単なる文字の打ち間違いというよりは、適切な関数選択の誤り、あるいはより良いプラクティスへの修正と解釈できます。
この変更により、エラーメッセージがより詳細かつ読みやすくなり、デバッグや問題特定が容易になるというメリットがあります。特に、エラーメッセージに変数や動的な情報を埋め込む必要がある場合には Fatalf
の使用が不可欠です。
前提知識の解説
Go言語の log
パッケージ
Go言語の標準ライブラリには、基本的なロギング機能を提供する log
パッケージが含まれています。このパッケージには、ログメッセージを出力し、必要に応じてプログラムを終了させる関数がいくつか提供されています。
log.Print(v ...any)
/log.Printf(format string, v ...any)
/log.Println(v ...any)
: これらの関数は、ログメッセージを出力しますが、プログラムの実行は継続します。Printf
はフォーマット文字列をサポートします。log.Fatal(v ...any)
: ログメッセージを出力した後、os.Exit(1)
を呼び出してプログラムを即座に終了させます。この際、defer
関数は実行されません。引数はそのまま出力されます。log.Fatalf(format string, v ...any)
:log.Fatal
と同様にログメッセージを出力し、os.Exit(1)
を呼び出してプログラムを終了させます。しかし、fmt.Printf
と同じようにフォーマット文字列と可変引数を受け取り、整形されたメッセージを出力できます。log.Panic(v ...any)
/log.Panicf(format string, v ...any)
: ログメッセージを出力した後、panic
を発生させます。panic
はdefer
関数を実行し、リカバリされない場合はプログラムを終了させます。
このコミットでは、log.Fatal
を log.Fatalf
に変更することで、エラーメッセージに動的な値を埋め込む際の柔軟性を高めています。
Go言語の testing
パッケージ
Go言語の testing
パッケージは、ユニットテストやベンチマークテストを記述するためのフレームワークを提供します。テスト関数は *testing.T
型の引数を受け取り、このオブジェクトを通じてテストの成否を報告したり、ログを出力したりします。
t.Log(args ...interface{})
/t.Logf(format string, args ...interface{})
: テスト中にログメッセージを出力します。テストの失敗には影響しません。Logf
はフォーマット文字列をサポートします。t.Error(args ...interface{})
/t.Errorf(format string, args ...interface{})
: テストを失敗としてマークしますが、テスト関数の実行は継続します。Errorf
はフォーマット文字列をサポートします。t.Fatal(args ...interface{})
: テストを失敗としてマークし、現在のテスト関数の実行を即座に停止します。t.FailNow()
を呼び出すのと同等です。引数はそのまま出力されます。t.Fatalf(format string, args ...interface{})
:t.Fatal
と同様にテストを失敗としてマークし、現在のテスト関数の実行を即座に停止します。しかし、fmt.Printf
と同じようにフォーマット文字列と可変引数を受け取り、整形されたメッセージを出力できます。
このコミットでは、t.Fatal
を t.Fatalf
に変更することで、テスト失敗時のメッセージに動的な値を埋め込む際の柔軟性を高め、より詳細な失敗理由を報告できるようにしています。
技術的詳細
このコミットの技術的な詳細は、主にGo言語の標準ライブラリにおけるロギングとテスト報告のベストプラクティスへの準拠にあります。
-
フォーマットされた出力の重要性:
log.Fatalf
やt.Fatalf
のようなフォーマット関数を使用することで、エラーメッセージやテスト失敗メッセージに動的な値を埋め込むことができます。例えば、log.Fatalf("unknown compiler %q", buildContext.Compiler)
のように、%q
などの動詞(verb)を使って変数の値を引用符で囲んで出力することで、デバッグ時にどの値が問題を引き起こしたのかを明確に把握できます。これは、単にlog.Fatal("unknown compiler", buildContext.Compiler)
と出力するよりもはるかに情報量が多く、デバッグ効率を向上させます。 -
一貫性の向上: Go言語のコードベース全体で、エラーメッセージやログ出力のスタイルを一貫させることは非常に重要です。
Printf
スタイルのフォーマット関数は、Go言語の標準的な出力方法であり、多くの開発者にとって馴染み深いものです。このコミットは、既存のコードベースにおけるこの一貫性を強化し、将来のコード記述においても同様のプラクティスを奨励するものです。 -
「typo」の解釈: コミットメッセージの「fix typo」という表現は、単なるスペルミス以上の意味合いを含んでいます。これは、
Fatal
とFatalf
の機能的な違いを理解し、より適切な関数を選択するという意味での「修正」と捉えるべきです。特に、エラーメッセージに変数を含める必要がある場合、Fatalf
の使用は必須であり、Fatal
を使用することは機能的な誤りとなります。 -
影響範囲: この変更は、
cmd/go
(Goコマンドラインツール)、cmd/godoc
(Goドキュメントツール)、src/pkg/net
(ネットワークパッケージ) の3つの異なるモジュールにわたっています。これは、Go言語の広範なコードベース全体で同様の「typo」が存在し、それらを一貫して修正する意図があったことを示唆しています。
コアとなるコードの変更箇所
src/cmd/go/build.go
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1112,7 +1112,7 @@ type toolchain interface {
type noToolchain struct{}
func noCompiler() error {
- log.Fatal("unknown compiler %q", buildContext.Compiler)
+ log.Fatalf("unknown compiler %q", buildContext.Compiler)
return nil
}
src/cmd/godoc/godoc.go
--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -88,7 +88,7 @@ func initHandlers() {
for _, p := range filepath.SplitList(*pkgPath) {
_, elem := filepath.Split(p)
if elem == "" {
- log.Fatal("invalid -path argument: %q has no final element", p)
+ log.Fatalf("invalid -path argument: %q has no final element", p)
}
fs.Bind("/src/pkg/"+elem, OS(p), "/", bindReplace)
}
src/pkg/net/unicast_test.go
--- a/src/pkg/net/unicast_test.go
+++ b/src/pkg/net/unicast_test.go
@@ -545,7 +545,7 @@ func TestProhibitionaryDialArgs(t *testing.T) {
for _, tt := range prohibitionaryDialArgTests {
_, err = Dial(tt.net, tt.addr+":"+port)
if err == nil {
- t.Fatal("Dial(%q, %q) should fail", tt.net, tt.addr)
+ t.Fatalf("Dial(%q, %q) should fail", tt.net, tt.addr)
}
}
}
コアとなるコードの解説
src/cmd/go/build.go
の変更
noCompiler
関数は、Goコンパイラが不明な場合に呼び出されるエラーハンドリングの一部です。
変更前: log.Fatal("unknown compiler %q", buildContext.Compiler)
変更後: log.Fatalf("unknown compiler %q", buildContext.Compiler)
ここでは、buildContext.Compiler
の値をエラーメッセージに含めるために、%q
というフォーマット動詞が使用されています。log.Fatal
はフォーマット文字列を解釈しないため、この %q
はリテラルとして出力されてしまい、期待通りの動作をしませんでした。log.Fatalf
に変更することで、buildContext.Compiler
の値が適切に引用符で囲まれて出力されるようになり、より正確で分かりやすいエラーメッセージが生成されます。
src/cmd/godoc/godoc.go
の変更
initHandlers
関数は、godoc
コマンドの初期化処理の一部で、-path
引数の検証を行っています。
変更前: log.Fatal("invalid -path argument: %q has no final element", p)
変更後: log.Fatalf("invalid -path argument: %q has no final element", p)
ここでも build.go
と同様に、p
の値をエラーメッセージに含めるために %q
が使われています。log.Fatal
では %q
がリテラルとして扱われるため、p
の値が適切に表示されませんでした。log.Fatalf
に変更することで、無効なパス引数が何であったかを正確に報告できるようになります。
src/pkg/net/unicast_test.go
の変更
TestProhibitionaryDialArgs
関数は、net
パッケージのテストコードの一部で、特定の無効な引数で Dial
関数を呼び出した際にエラーが発生することを確認しています。
変更前: t.Fatal("Dial(%q, %q) should fail", tt.net, tt.addr)
変更後: t.Fatalf("Dial(%q, %q) should fail", tt.net, tt.addr)
このテストでは、Dial
が失敗すべき状況で成功した場合に t.Fatal
を呼び出してテストを失敗させていました。tt.net
と tt.addr
の値をエラーメッセージに含めるために %q
が使用されていますが、t.Fatal
はフォーマット文字列を解釈しません。t.Fatalf
に変更することで、どの Dial
呼び出しが予期せず成功したのかを明確に特定できる、整形されたエラーメッセージが出力されるようになります。これにより、テストのデバッグが容易になります。
関連リンク
- Go CL 5757050: https://golang.org/cl/5757050
参考にした情報源リンク
-
Go言語
log.Fatal()
とlog.Fatalf()
の違い:- https://go.dev/pkg/log/#Fatal
- https://go.dev/pkg/log/#Fatalf
- https://zerotohero.dev/go-log-fatal-vs-panic/
- https://itnext.io/go-logging-best-practices-and-patterns-d0122122122
- https://stackoverflow.com/questions/20445925/how-to-use-log-fatal-in-go
- https://middleware.io/blog/golang-logging/
- https://medium.com/@prashant.sharma.01/go-logging-best-practices-and-patterns-d0122122122
- https://hashnode.dev/go-logging-best-practices-and-patterns-d0122122122
- https://quasilyte.dev/blog/go-logging-best-practices-and-patterns/
- https://www.reddit.com/r/golang/comments/1122222/logfatal_vs_panic/
-
Go言語
testing.T.Fatal()
とtesting.T.Fatalf()
の違い:- https://dev.to/ankit0101/t-error-vs-t-fatal-in-go-testing-3222
- https://gobyexample.com/testing
- https://go.dev/pkg/testing/#T.Fatal
- https://go.dev/pkg/testing/#T.Fatalf
- https://www.jetbrains.com/help/go/testing.html
- https://www.cloudbees.com/blog/go-testing-best-practices
- https://bitfieldconsulting.com/golang/testing
- https://www.reddit.com/r/golang/comments/1122222/t_error_vs_t_fatal_in_go_testing/