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

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

このコミットは、Go言語の標準ライブラリである encoding/binary パッケージのテストコードにおける型定義の誤りを修正するものです。具体的には、テストヘルパー関数 checkResult の引数 order の型が正しく指定されていなかった問題を修正し、意図せず動作していたテストの振る舞いを明確にしました。

コミット

  • コミットハッシュ: 7f91a39d3d520d99f988d7060237550f11b6ab18
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: 2011年10月27日 木曜日 19:38:57 -0700

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

https://github.com/golang/go/commit/7f91a39d3d520d99f988d7060237550f11b6ab18

元コミット内容

encoding/binary: fix type in test

Was working only accidentally.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5303082

変更の背景

このコミットの背景には、Go言語の型システムにおける特定の挙動と、それによって引き起こされたテストコードの潜在的な脆弱性があります。コミットメッセージにある「Was working only accidentally.(たまたま動いていただけだった)」という記述がその核心を示しています。

Go言語では、複数の変数をカンマで区切って宣言する際に、最後の変数に型を指定すると、それ以前の変数も同じ型として推論されるという構文があります。しかし、このコミットが修正しているケースでは、order, err os.Error という記述がありました。これは ordererr の両方が os.Error 型であると解釈されるべきですが、実際には order の型が正しく推論されず、interface{} 型として扱われていた可能性があります。

encoding/binary パッケージのテストにおいて、checkResult 関数は ByteOrder 型の引数を期待していました。もし orderinterface{} 型として渡されていた場合でも、Goのインターフェースの仕組みにより、ByteOrder インターフェースを満たす具体的な型(例: binary.LittleEndianbinary.BigEndian)が渡されれば、実行時には問題なく動作してしまうことがあります。しかし、これは厳密な型チェックが行われていない状態であり、将来的なコード変更やコンパイラの挙動変更によって予期せぬエラーを引き起こす可能性を秘めていました。

このコミットは、このような「たまたま動いていた」状態を解消し、テストコードの型安全性を向上させることを目的としています。これにより、コードの意図が明確になり、将来的なメンテナンス性や堅牢性が確保されます。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と標準ライブラリの知識が必要です。

  1. encoding/binary パッケージ:

    • Go言語の標準ライブラリの一つで、数値データをバイト列に変換(エンコード)したり、バイト列から数値データに変換(デコード)したりするための機能を提供します。
    • 特に、異なるエンディアン(バイト順序)での変換をサポートしており、ネットワーク通信やファイルI/Oなどでバイト列を扱う際に非常に重要です。
    • エンディアンには主に「ビッグエンディアン(Big-Endian)」と「リトルエンディアン(Little-Endian)」があります。
      • ビッグエンディアン: データの最上位バイト(最も大きな値を持つバイト)が、最も小さいアドレスに格納されます。人間が数値を読む順序に近いため、「ネットワークバイトオーダー」とも呼ばれます。
      • リトルエンディアン: データの最下位バイト(最も小さな値を持つバイト)が、最も小さいアドレスに格納されます。Intel系のCPUなどで広く採用されています。
  2. ByteOrder インターフェース:

    • encoding/binary パッケージ内で定義されているインターフェースです。
    • Uint16(), Uint32(), Uint64(), PutUint16(), PutUint32(), PutUint64() などのメソッドを持ち、指定されたエンディアンでバイト列と数値の変換を行うための振る舞いを定義します。
    • binary.LittleEndianbinary.BigEndian は、この ByteOrder インターフェースを実装した具体的な型(構造体)であり、それぞれリトルエンディアンとビッグエンディアンでの変換ロジックを提供します。
  3. os.Error (Go 1.0以前のエラーインターフェース):

    • このコミットが作成された2011年時点のGo言語(Go 1.0リリース前)では、エラーを表すための標準インターフェースとして os.Error が使用されていました。
    • os.Error は単一の String() string メソッドを持つインターフェースでした。
    • Go 1.0のリリースに伴い、os.Error は非推奨となり、より汎用的な組み込みの error インターフェース(これも Error() string メソッドを持つ)に置き換えられました。このコミットのコードスニペットに os.Error が残っているのは、当時のGoのバージョンを反映しているためです。
  4. interface{} (空インターフェース):

    • Go言語における空インターフェース interface{} は、任意の型の値を保持できる特別なインターフェースです。
    • Goの型システムにおいて、型が明示的に指定されていない場合や、複数の変数をカンマで区切って宣言する際に型が正しく推論されない場合に、暗黙的に interface{} 型として扱われることがあります。
    • interface{} 型の変数は、実行時にその中に格納されている具体的な値の型を動的にチェック(型アサーションや型スイッチ)することができます。
  5. *testing.T:

    • Go言語の標準テストパッケージ testing で提供される型です。
    • テスト関数(TestXxx という命名規則に従う関数)の引数として渡され、テストの実行状態を管理したり、エラーを報告したりするためのメソッド(例: t.Errorf(), t.Fatalf(), t.Logf() など)を提供します。

技術的詳細

このコミットの技術的な核心は、Go言語の関数シグネチャにおける引数の型宣言の厳密化にあります。

変更前の checkResult 関数のシグネチャは以下のようになっていました。

func checkResult(t *testing.T, dir string, order, err os.Error, have, want interface{}) {

ここで注目すべきは order, err os.Error の部分です。Go言語では、以下のように複数の変数をまとめて宣言し、最後の変数に型を指定すると、前の変数も同じ型として扱われます。

var a, b int // aもbもint型

しかし、このルールは、型がインターフェース型である場合に、コンパイラが order の型を os.Error ではなく interface{} と誤って推論してしまうケースがあったようです。つまり、orderos.Error 型であるべきなのに、実際には interface{} 型として扱われていた可能性が高いです。

ByteOrder はインターフェースであり、os.Error もインターフェースです。Goのインターフェースは、そのインターフェースが定義するメソッドセットを実装していれば、そのインターフェース型として扱われます。binary.LittleEndianbinary.BigEndianByteOrder インターフェースを実装していますが、同時に os.Error インターフェースは実装していません。

もし orderinterface{} 型として扱われていた場合、checkResult 関数内で orderByteOrder 型として期待される操作(例: order.Uint32(...) のようなメソッド呼び出し)が行われても、実行時に orderByteOrder を実装した具体的な値が格納されていれば、Goの動的なインターフェースディスパッチの仕組みにより、エラーなく動作してしまいます。これは、コンパイル時には型エラーにならず、実行時に初めて問題が顕在化する可能性を秘めていました。

このコミットは、order 引数の型を明示的に ByteOrder と指定することで、この曖昧さを解消し、コンパイル時に厳密な型チェックが行われるように修正しています。

func checkResult(t *testing.T, dir string, order ByteOrder, err os.Error, have, want interface{}) {

これにより、checkResult 関数に ByteOrder インターフェースを実装していない値が order 引数として渡された場合、コンパイル時に型エラーが発生するようになり、テストコードの堅牢性が向上しました。これは、Go言語の「静的型付け」の利点を最大限に活かすための重要な修正と言えます。

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

変更は src/pkg/encoding/binary/binary_test.go ファイルの1箇所のみです。

--- a/src/pkg/encoding/binary/binary_test.go
+++ b/src/pkg/encoding/binary/binary_test.go
@@ -99,7 +99,7 @@
 var src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
 var res = []int32{0x01020304, 0x05060708}

-func checkResult(t *testing.T, dir string, order, err os.Error, have, want interface{}) {
+func checkResult(t *testing.T, dir string, order ByteOrder, err os.Error, have, want interface{}) {
  	if err != nil {
  		t.Errorf("%v %v: %v", dir, order, err)
  		return

コアとなるコードの解説

変更されたのは checkResult 関数のシグネチャです。

  • 変更前:

    func checkResult(t *testing.T, dir string, order, err os.Error, have, want interface{}) {
    

    この行では、ordererr の両方が os.Error 型であると意図されていた可能性がありますが、Goのコンパイラが orderinterface{} 型として推論していた可能性があります。これは、orderByteOrder インターフェースを実装していることを保証しないため、潜在的な型不一致のリスクがありました。

  • 変更後:

    func checkResult(t *testing.T, dir string, order ByteOrder, err os.Error, have, want interface{}) {
    

    この変更により、order 引数の型が明示的に ByteOrder インターフェース型として指定されました。これにより、コンパイラは orderByteOrder インターフェースのすべてのメソッドを実装していることを厳密にチェックするようになります。もし checkResult 関数が呼び出される際に、orderByteOrder を実装していない値が渡された場合、コンパイル時にエラーが発生し、誤った型が渡されることを防ぐことができます。

この修正は、テストコードの型安全性を高め、将来的なリファクタリングやGo言語のバージョンアップによる予期せぬ動作変更のリスクを低減する上で非常に重要です。

関連リンク

  • Go CL (Change List): https://golang.org/cl/5303082
    • Goプロジェクトにおけるコード変更のレビューシステム(Gerritベース)のリンクです。このリンクから、このコミットがマージされるまでの議論やレビューの履歴を確認できます。

参考にした情報源リンク