[インデックス 10900] ファイルの概要
このコミットは、Go言語の標準ライブラリにおけるテスト関数で頻繁に発生していたpanic
の使用を、testing.B
型の新しい機能を利用して適切な方法に改善するものです。Rob Pikeにより2011年12月20日に実装されました。
コミット
コミットハッシュ: 6b772462e420d15f5e1669a5f03e4f1cb7d8f2af
作成者: Rob Pike r@golang.org
日付: 2011年12月20日 10:36:25 -0800
レビュー: golang-dev, rsc
Code Review: https://golang.org/cl/5498045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6b772462e420d15f5e1669a5f03e4f1cb7d8f2af
元コミット内容
コミットメッセージ:
panics: use the new facilities of testing.B instead
Lots of panics go away.
Also fix a name error in html/template.
R=golang-dev, rsc
CC=golang-dev
変更されたファイル数: 18ファイル
挿入行数: 54行
削除行数: 73行
変更されたファイルは、bytes
、crypto/aes
、encoding/binary
、encoding/gob
、encoding/json
、html/template
、image/draw
、image/jpeg
、image/png
、image/tiff
、math/big
、net/http
、net/rpc
、old/regexp
、regexp
、strings
パッケージのテストファイルです。
変更の背景
このコミットが実装された2011年当時、Go言語の標準ライブラリのベンチマークテストやテスト関数では、期待しない値が発生した場合にpanic
を使用してテストを停止する方法が一般的でした。しかし、この手法には以下の問題がありました:
- テストの異常終了:
panic
はプログラム全体を異常終了させるため、テストフレームワークの適切な終了処理が実行されない - デバッグ情報の不足:
panic
による終了では、詳細なエラー情報やスタックトレースが適切に記録されない場合がある - テストの継続性:
panic
が発生すると、後続のテストが実行されない - ベンチマークの測定精度: ベンチマーク実行中の
panic
は、正確な性能測定を阻害する
Rob Pikeは、Goにおけるエラーハンドリングの哲学として「パニックは本当に例外的な状況でのみ使用すべきである」と考えており、テストフレームワークが提供する適切なエラーハンドリング機能を使用することを推奨していました。
前提知識の解説
Go言語のテストフレームワーク
Go言語のtesting
パッケージは、単体テストとベンチマークテストの両方をサポートしています。
testing.B型の主要メソッド
- Fatal(args ...interface{}): エラーメッセージを出力し、テストを即座に終了
- Fatalf(format string, args ...interface{}): フォーマットされたエラーメッセージを出力し、テストを即座に終了
- StartTimer(): ベンチマークタイマーを開始
- StopTimer(): ベンチマークタイマーを停止
- SetBytes(n int64): 1回のベンチマーク実行で処理されるバイト数を設定
panic vs Fatal の違い
- panic: プログラム全体を異常終了させる。
defer
文は実行されるが、テストフレームワークの終了処理は適切に実行されない場合がある - Fatal: テストフレームワークが提供する適切な終了方法。エラー情報を記録し、テストの状態を適切に管理
Go言語のエラーハンドリング哲学
Rob Pikeを含むGoの設計者たちは、以下の原則を重視していました:
- エラーは値として扱う: エラーは例外ではなく、通常の値として扱うべき
- panic は例外的な状況でのみ使用: プログラムの継続が不可能な場合のみ
- 明示的なエラーハンドリング: エラーの可能性を明示的に処理する
技術的詳細
主な変更パターン
このコミットでは、以下の3つの主要な変更パターンが適用されました:
1. 単純なpanic → Fatal変換
// 変更前
if err != nil {
panic("NewCipher")
}
// 変更後
if err != nil {
b.Fatal("NewCipher:", err)
}
2. println + panic → Fatal変換
// 変更前
if j != n-1 {
println("bad index", j)
panic("bad index")
}
// 変更後
if j != n-1 {
b.Fatal("bad index", j)
}
3. fmt.Printf + panic → Fatalf変換
// 変更前
if a+b != c {
fmt.Printf("Add: expected %d got %d", a+b, c)
panic("rpc error")
}
// 変更後
if A+B != C {
b.Fatalf("rpc error: Add: expected %d got %d", A+B, C)
}
html/templateパッケージの修正
さらに、このコミットではhtml/template
パッケージのエラーメッセージに含まれる誤ったパッケージ名も修正されました:
// 変更前
return fmt.Sprintf("exp/template/html:%s:%d: %s", e.Name, e.Line, e.Description)
// 変更後
return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description)
この修正により、エラーメッセージが正しいパッケージ名を表示するようになりました。
コアとなるコードの変更箇所
1. bytes/bytes_test.go
ベンチマーク関数内でのインデックス検証とカウント検証の改善:
func bmIndexByte(b *testing.B, index func([]byte, byte) int, n int) {
// 省略
for i := 0; i < b.N; i++ {
j := index(buf, 'x')
if j != n-1 {
b.Fatal("bad index", j) // panic から変更
}
}
}
2. net/rpc/server_test.go
RPC通信のベンチマーク関数における詳細なエラーハンドリング:
func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) {
// 省略
if err != nil {
b.Fatalf("rpc error: Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
b.Fatalf("rpc error: Add: expected %d got %d", reply.C, args.A+args.B)
}
}
3. encoding/json/bench_test.go
JSON エンコーディング・デコーディングのベンチマーク関数:
func BenchmarkCodeEncoder(b *testing.B) {
// 省略
for i := 0; i < b.N; i++ {
if err := enc.Encode(&codeStruct); err != nil {
b.Fatal("Encode:", err) // panic(err) から変更
}
}
}
コアとなるコードの解説
ベンチマーク関数の改善点
- エラー情報の詳細化:
panic
では失われがちな詳細なエラー情報がFatal
/Fatalf
により適切に記録される - テストの継続性:
Fatal
はテストフレームワークが提供する適切な終了方法であり、後続のテストの実行を妨げない - デバッグの容易性: より詳細なエラーメッセージにより、問題の特定が容易になる
性能測定の精度向上
panic
による異常終了は、ベンチマークの正確な測定を阻害しますが、Fatal
を使用することで:
- タイマーの適切な停止
- リソースの適切な解放
- 統計情報の正確な記録
が可能になります。
変数名の改善
net/rpc/server_test.go
では、変数名の衝突を避けるため、以下の変更も行われました:
// 変更前
a := call.Args.(*Args).A
b := call.Args.(*Args).B // testing.B と名前が衝突
c := call.Reply.(*Reply).C
// 変更後
A := call.Args.(*Args).A
B := call.Args.(*Args).B // 大文字に変更
C := call.Reply.(*Reply).C
これにより、testing.B
の変数b
との名前衝突が解消されました。