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

[インデックス 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行

変更されたファイルは、bytescrypto/aesencoding/binaryencoding/gobencoding/jsonhtml/templateimage/drawimage/jpegimage/pngimage/tiffmath/bignet/httpnet/rpcold/regexpregexpstringsパッケージのテストファイルです。

変更の背景

このコミットが実装された2011年当時、Go言語の標準ライブラリのベンチマークテストやテスト関数では、期待しない値が発生した場合にpanicを使用してテストを停止する方法が一般的でした。しかし、この手法には以下の問題がありました:

  1. テストの異常終了: panicはプログラム全体を異常終了させるため、テストフレームワークの適切な終了処理が実行されない
  2. デバッグ情報の不足: panicによる終了では、詳細なエラー情報やスタックトレースが適切に記録されない場合がある
  3. テストの継続性: panicが発生すると、後続のテストが実行されない
  4. ベンチマークの測定精度: ベンチマーク実行中のpanicは、正確な性能測定を阻害する

Rob Pikeは、Goにおけるエラーハンドリングの哲学として「パニックは本当に例外的な状況でのみ使用すべきである」と考えており、テストフレームワークが提供する適切なエラーハンドリング機能を使用することを推奨していました。

前提知識の解説

Go言語のテストフレームワーク

Go言語のtestingパッケージは、単体テストとベンチマークテストの両方をサポートしています。

testing.B型の主要メソッド

  1. Fatal(args ...interface{}): エラーメッセージを出力し、テストを即座に終了
  2. Fatalf(format string, args ...interface{}): フォーマットされたエラーメッセージを出力し、テストを即座に終了
  3. StartTimer(): ベンチマークタイマーを開始
  4. StopTimer(): ベンチマークタイマーを停止
  5. SetBytes(n int64): 1回のベンチマーク実行で処理されるバイト数を設定

panic vs Fatal の違い

  • panic: プログラム全体を異常終了させる。defer文は実行されるが、テストフレームワークの終了処理は適切に実行されない場合がある
  • Fatal: テストフレームワークが提供する適切な終了方法。エラー情報を記録し、テストの状態を適切に管理

Go言語のエラーハンドリング哲学

Rob Pikeを含むGoの設計者たちは、以下の原則を重視していました:

  1. エラーは値として扱う: エラーは例外ではなく、通常の値として扱うべき
  2. panic は例外的な状況でのみ使用: プログラムの継続が不可能な場合のみ
  3. 明示的なエラーハンドリング: エラーの可能性を明示的に処理する

技術的詳細

主な変更パターン

このコミットでは、以下の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) から変更
        }
    }
}

コアとなるコードの解説

ベンチマーク関数の改善点

  1. エラー情報の詳細化: panicでは失われがちな詳細なエラー情報がFatal/Fatalfにより適切に記録される
  2. テストの継続性: Fatalはテストフレームワークが提供する適切な終了方法であり、後続のテストの実行を妨げない
  3. デバッグの容易性: より詳細なエラーメッセージにより、問題の特定が容易になる

性能測定の精度向上

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との名前衝突が解消されました。

関連リンク

参考にした情報源リンク