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

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

このコミットは、Go言語の初期開発段階におけるcontainer/arrayパッケージの機能拡張とテスト改善に関するものです。具体的には、配列要素がソート可能であるためのインターフェースLessInterfaceを導入し、ソート機能の完全なサポートを目指しています。また、テストレポートの質を向上させるために、テスト失敗時の挙動をt.FailNow()からt.Error()t.Errorf()に変更しています。

コミット

commit 9af3ee5471c612813d08c1ebedfa507d46fad615
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Nov 19 16:23:45 2008 -0800

    - full support for sorting (assumes array elements implement LessInterface
    - better test reporting
    
    R=r
    DELTA=43  (24 added, 0 deleted, 19 changed)
    OCL=19641
    CL=19645

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

https://github.com/golang/go/commit/9af3ee5471c612813d08c1ebedfa507d46fad615

元コミット内容

    - full support for sorting (assumes array elements implement LessInterface
    - better test reporting

変更の背景

このコミットが行われた2008年11月は、Go言語がまだGoogle社内で開発されており、一般に公開される前の非常に初期の段階でした。Go言語は2009年11月に初めて一般公開され、バージョン1.0がリリースされたのは2012年3月です。したがって、このコミットはGo言語の基本的なデータ構造とユーティリティライブラリを構築する過程の一部として行われました。

変更の主な背景は以下の2点です。

  1. ソート機能の完全なサポート: container/arrayパッケージは、汎用的な配列データ構造を提供するものであり、その要素をソートする機能は非常に重要です。このコミット以前にも部分的なソートインターフェースのサポートがあったようですが、このコミットではLessInterfaceという専用のインターフェースを導入することで、より堅牢で完全なソート機能を提供することを目指しました。これにより、配列内の任意の要素が比較可能であれば、Goの標準ソートアルゴリズム(後のsortパッケージ)と連携してソートできるようになります。
  2. テストレポートの改善: ソフトウェア開発において、テストはコードの品質と信頼性を保証するために不可欠です。テストが失敗した際に、その原因を特定しやすくするための明確なレポートは開発効率に直結します。このコミットでは、テストフレームワークのt.FailNow()の使用をt.Error()t.Errorf()に変更することで、テスト失敗時の挙動を改善し、より詳細な情報を提供できるようにしました。t.FailNow()はテストを即座に終了させるのに対し、t.Error()はテストを継続しながら失敗を記録するため、一つのテスト関数内で複数のアサーションを行い、すべての失敗箇所を報告したい場合に有用です。

前提知識の解説

Go言語の初期の「export」キーワード(と現在の可視性ルール)

このコミットのコードにはexport type LessInterface interface { ... }export func TestInit(t *testing.T) { ... }といったexportキーワードが見られます。しかし、現在のGo言語にはexportという明示的なキーワードは存在しません。

Go言語では、識別子(変数、関数、型など)の可視性は、その名前の先頭文字が大文字か小文字かによって決定されます。

  • 大文字で始まる識別子: パッケージ外からアクセス可能(エクスポートされる、publicに相当)。
  • 小文字で始まる識別子: パッケージ内でのみアクセス可能(エクスポートされない、privateに相当)。

このコミットに見られるexportキーワードは、Go言語の非常に初期の実験的な段階で存在した可能性を示唆しています。Go言語の設計思想は「シンプルさ」と「明示的なキーワードの削減」を重視しているため、後のバージョンでこのexportキーワードは廃止され、現在の命名規則による可視性ルールに落ち着いたと考えられます。この変更は、Go言語がその設計原則を確立していく過程で、より簡潔で一貫性のある方法を模索していた証拠と言えるでしょう。

Go言語のインターフェース

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。他の多くのオブジェクト指向言語とは異なり、Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されているすべてのメソッドを実装していれば、その型はそのインターフェースを実装しているとみなされます。implementsのようなキーワードは不要です。

このコミットで導入されたLessInterfaceは、Less(y Element) boolというメソッドを定義しています。これは、配列の要素が他の要素と比較可能である(「より小さい」という関係を定義できる)ことを示すためのものです。

export type LessInterface interface {
	Less(y Element) bool
}

このインターフェースの導入により、Array型は、その要素がLessInterfaceを実装していることを前提として、ソート操作を実行できるようになります。これは、Go言語が持つ強力なポリモーフィズムのメカニズムであり、異なる具体的な型が同じインターフェースを実装することで、共通の操作(この場合はソート)を適用できる柔軟性を提供します。

Go言語のテストフレームワークにおけるt.FailNow()t.Error()/t.Errorf()

Go言語の標準テストパッケージtestingは、テストの実行と結果の報告のための基本的な機能を提供します。*testing.T型は、テスト関数内でテストの状態を管理し、失敗を報告するためのメソッドを提供します。

  • t.FailNow(): このメソッドが呼び出されると、現在のテスト関数は即座に終了します。t.FailNow()の後に続くコードは実行されません。これは、テストの前提条件が満たされない場合や、それ以上テストを継続しても意味がないような致命的なエラーが発生した場合に利用されます。例えば、テストのセットアップが失敗した場合などです。
  • t.Error() / t.Errorf(): これらのメソッドが呼び出されると、テストは失敗としてマークされますが、現在のテスト関数の実行は継続されます。t.Error()は引数をそのままエラーメッセージとして出力し、t.Errorf()fmt.Printfのようなフォーマット文字列と引数を受け取り、より詳細なエラーメッセージを生成できます。これらのメソッドは、一つのテスト関数内で複数のアサーションを行い、すべての失敗箇所を報告したい場合に特に有用です。例えば、テーブル駆動テストで複数のテストケースを検証する際に、一つのケースが失敗しても他のケースの検証を続けたい場合などです。

このコミットでは、既存のテストコードでt.FailNow()が使われていた箇所をt.Error()t.Errorf()に置き換えています。これは、テストの網羅性を高め、一つのテスト実行でより多くの失敗情報を収集できるようにするための改善と解釈できます。

技術的詳細

このコミットは、Go言語のsrc/lib/container/array/array.gosrc/lib/container/array/testarray.goの2つのファイルに影響を与えています。

src/lib/container/array/array.goの変更

このファイルでは、Array型にソート機能のためのサポートが追加されています。

  1. LessInterfaceの定義: export type LessInterface interface { Less(y Element) bool } このインターフェースは、Lessという単一のメソッドを定義しています。このメソッドは、Element型の引数yを受け取り、レシーバーの要素がyよりも小さい場合にtrueを返します。これにより、配列の要素が比較可能であるという契約が明示されます。

  2. Array型へのLessメソッドの追加: func (p *Array) Less(i, j int) bool { return p.a[i].(LessInterface).Less(p.a[j]) } このメソッドは、sort.Interface(Goの標準ソートパッケージが要求するインターフェース)の一部として機能します。i番目の要素がj番目の要素よりも小さいかどうかを判断するために、p.a[i]p.a[j]LessInterfaceに型アサーションし、そのLessメソッドを呼び出しています。これは、Arrayが汎用的なElementを保持するため、具体的な比較ロジックはElement自身が提供する必要があるという設計を示しています。

これらの変更により、Array型は、その要素がLessInterfaceを実装していれば、Goの標準ソートアルゴリズム(後のsortパッケージ)と組み合わせてソートできるようになります。

src/lib/container/array/testarray.goの変更

このファイルでは、既存のテスト関数における失敗報告のメカニズムが改善されています。

  1. t.FailNow()からt.Error()/t.Errorf()への変更: TestInit, TestNew, TestAccess, TestInsertRemoveClearといった既存のテスト関数内で、テストが失敗した際に呼び出されていたt.FailNow()が、t.Error()またはt.Errorf()に置き換えられています。

    • 例えば、if a.Init(0).Len() != 0 { t.FailNow() }if a.Init(0).Len() != 0 { t.Error("A") }に変更されています。
    • より複雑なケースでは、t.Errorf("A wrong len %d (expected %d)", a.Len(), i)のように、フォーマット文字列を使って詳細なエラーメッセージを出力するように変更されています。
  2. コメントアウトされたTestSorting関数: コミットの差分には、/* currently doesn't compile due to linker bug ... */というコメントアウトされたTestSorting関数が含まれています。これは、ソート機能のテストを試みたものの、当時のリンカーのバグによりコンパイルできなかったことを示しています。このコメントは、Go言語の初期開発における課題や、機能が段階的に追加されていく様子を垣間見ることができます。

これらのテスト変更は、テストが失敗しても即座に終了せず、可能な限り多くの失敗情報を収集し、より詳細なレポートを提供することを目的としています。これにより、開発者はテストの失敗原因をより効率的に特定できるようになります。

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

diff --git a/src/lib/container/array/array.go b/src/lib/container/array/array.go
index 97f2c43970..95ed6c2ece 100644
--- a/src/lib/container/array/array.go
+++ b/src/lib/container/array/array.go
@@ -111,6 +111,17 @@ func (p *Array) Pop() Element {
 
 
 // Partial SortInterface support
++
+export type LessInterface interface {
+	Less(y Element) bool
+}
++
++
+func (p *Array) Less(i, j int) bool {
+	return p.a[i].(LessInterface).Less(p.a[j])
+}
++
++
 func (p *Array) Swap(i, j int) {
  	a := p.a;
  	a[i], a[j] = a[j], a[i]
diff --git a/src/lib/container/array/testarray.go b/src/lib/container/array/testarray.go
index be4928301a..2c56ba8f02 100644
--- a/src/lib/container/array/testarray.go
+++ b/src/lib/container/array/testarray.go
@@ -8,18 +8,19 @@ import "array"
 import "testing"
 import "sort"
 
++
 export func TestInit(t *testing.T) {
  	var a array.Array;
--	if a.Init(0).Len() != 0 { t.FailNow() }
--	if a.Init(1).Len() != 1 { t.FailNow() }
--	if a.Init(10).Len() != 10 { t.FailNow() }
++	if a.Init(0).Len() != 0 { t.Error("A") }
++	if a.Init(1).Len() != 1 { t.Error("B") }
++	if a.Init(10).Len() != 10 { t.Error("C") }
  }
  
  
  export func TestNew(t *testing.T) {
--	if array.New(0).Len() != 0 { t.FailNow() }
--	if array.New(1).Len() != 1 { t.FailNow() }
--	if array.New(10).Len() != 10 { t.FailNow() }
++	if array.New(0).Len() != 0 { t.Error("A") }
++	if array.New(1).Len() != 1 { t.Error("B") }
++	if array.New(10).Len() != 10 { t.Error("C") }
  }
  
  
@@ -36,7 +37,7 @@ export func TestAccess(t *testing.T) {
  	\ta.Set(i, Val(i));
  	}
  	for i := 0; i < n; i++ {\
--	\tif a.At(i).(int) != Val(i) { t.FailNow() }\
++	\tif a.At(i).(int) != Val(i) { t.Error(i) }\
  	}
  }
  
@@ -46,24 +47,24 @@ export func TestInsertRemoveClear(t *testing.T) {
  	\ta := array.New(0);
  
  	for i := 0; i < n; i++ {\
--	\tif a.Len() != i { t.FailNow() }\
++	\tif a.Len() != i { t.Errorf("A wrong len %d (expected %d)", a.Len(), i) }\
  	\ta.Insert(0, Val(i));
--	\tif a.Last().(int) != Val(0) { t.FailNow() }\
++	\tif a.Last().(int) != Val(0) { t.Error("B") }\
  	}
  	for i := n-1; i >= 0; i-- {\
--	\tif a.Last().(int) != Val(0) { t.FailNow() }\
--	\tif a.Remove(0).(int) != Val(i) { t.FailNow() }\
--	\tif a.Len() != i { t.FailNow() }\
++	\tif a.Last().(int) != Val(0) { t.Error("C") }\
++	\tif a.Remove(0).(int) != Val(i) { t.Error("D") }\
++	\tif a.Len() != i { t.Errorf("E wrong len %d (expected %d)", a.Len(), i) }\
  	}
  
--	\tif a.Len() != 0 { t.FailNow() }\
++	\tif a.Len() != 0 { t.Errorf("F wrong len %d (expected 0)", a.Len()) }\
  	for i := 0; i < n; i++ {\
  	\ta.Push(Val(i));
--	\tif a.Len() != i+1 { t.FailNow() }\
--	\tif a.Last().(int) != Val(i) { t.FailNow() }\
++	\tif a.Len() != i+1 { t.Errorf("G wrong len %d (expected %d)", a.Len(), i+1) }\
++	\tif a.Last().(int) != Val(i) { t.Error("H") }\
  	}
  \ta.Init(0);\
--	\tif a.Len() != 0 { t.FailNow() }\
++	\tif a.Len() != 0 { t.Errorf("I wrong len %d (expected 0)", a.Len()) }\
  
  	const m = 5;\
  	for j := 0; j < m; j++ {\
@@ -71,9 +72,21 @@ export func TestInsertRemoveClear(t *testing.T) {\
  	\t\tfor i := 0; i < n; i++ {\
  	\t\t\tx := Val(i);\
  	\t\t\ta.Push(x);\
--	\t\t\tif a.Pop().(int) != x { t.FailNow() }\
--	\t\t\t\tif a.Len() != j+1 { t.FailNow() }\
++	\t\t\tif a.Pop().(int) != x { t.Error("J") }\
++	\t\t\tif a.Len() != j+1 { t.Errorf("K wrong len %d (expected %d)", a.Len(), j+1) }\
  	\t\t}\
  	}\
--	\tif a.Len() != m { t.FailNow() }\
++	\tif a.Len() != m { t.Errorf("L wrong len %d (expected %d)", a.Len(), m) }\
++}
++
++
+/* currently doesn't compile due to linker bug
+export func TestSorting(t *testing.T) {
+	const n = 100;
+	a := array.NewIntArray(n);
+	for i := n-1; i >= 0; i-- {
+		a.Set(i, n-1-i);
+	}
+	if sort.IsSorted(a) { t.Error("not sorted") }
+}
+*/

コアとなるコードの解説

src/lib/container/array/array.go

このファイルへの変更は、Array型がソート操作をサポートするための基盤を構築しています。

  1. LessInterfaceの導入: export type LessInterface interface { Less(y Element) bool } これは、Go言語のインターフェースの典型的な使用例です。LessInterfaceは、Lessという単一のメソッドを定義しており、このメソッドを持つ任意の型がLessInterfaceを実装していると見なされます。これにより、Array内の要素がどのように比較されるべきかを抽象化し、ソートアルゴリズムが要素の具体的な型に依存しないようにします。Elementは、Arrayが保持する汎用的な型を表していると考えられます。

  2. Array型へのLessメソッドの実装: func (p *Array) Less(i, j int) bool { return p.a[i].(LessInterface).Less(p.a[j]) } このLessメソッドは、Array型がsort.Interface(Goの標準ライブラリのソート機能が要求するインターフェース)の一部として機能するために追加されました。sort.Interfaceは、Len(), Swap(i, j int), Less(i, j int)の3つのメソッドを要求します。このコミットでは、Less(i, j int)が追加され、Arrayi番目の要素とj番目の要素を比較します。 注目すべきは、p.a[i].(LessInterface).Less(p.a[j])という部分です。これは、Arrayの内部スライスp.aからi番目の要素を取り出し、それをLessInterface型に型アサーションしています。そして、そのアサーションされたインターフェースのLessメソッドを呼び出し、j番目の要素を引数として渡しています。このメカニズムにより、Array自体は要素の比較ロジックを知る必要がなく、要素自身が比較方法を定義するという、Goらしい柔軟な設計が実現されています。

src/lib/container/array/testarray.go

このファイルへの変更は、テストの堅牢性と情報提供能力を向上させることを目的としています。

  1. t.FailNow()からt.Error()/t.Errorf()への変更: 既存のテスト関数(TestInit, TestNew, TestAccess, TestInsertRemoveClear)において、テスト失敗時にt.FailNow()が呼び出されていた箇所が、t.Error()またはt.Errorf()に置き換えられました。

    • t.FailNow()は、テストが失敗するとそのテスト関数を即座に終了させます。これは、テストの初期設定が失敗した場合など、それ以上テストを継続しても意味がない場合に有効です。
    • 一方、t.Error()t.Errorf()は、テストを失敗としてマークしますが、テスト関数の実行は継続されます。これにより、一つのテスト関数内で複数のアサーションを行っている場合でも、すべての失敗箇所が報告されるようになります。例えば、TestInsertRemoveClear関数では、t.Errorf("A wrong len %d (expected %d)", a.Len(), i)のように、期待値と実際の値をエラーメッセージに含めることで、より詳細なデバッグ情報を提供しています。この変更は、テストの網羅性を高め、開発者がテスト失敗の原因をより迅速に特定できるようにするための重要な改善です。
  2. コメントアウトされたTestSorting関数: /* currently doesn't compile due to linker bug ... */というコメントアウトされたブロックは、ソート機能のテストを実装しようとしたものの、当時のGo言語のリンカーにバグがあったためにコンパイルできなかったことを示しています。このコードは、array.NewIntArray(n)で整数配列を初期化し、逆順に要素を設定した後、sort.IsSorted(a)でソートされているかを確認しようとしています。このコメントは、Go言語の初期開発がまだ不安定であり、基本的な機能の実装においても様々な技術的課題に直面していたことを物語っています。

これらの変更は、Go言語の初期段階において、基本的なデータ構造の機能強化と、開発プロセスをサポートするためのテストインフラの改善が並行して進められていたことを明確に示しています。

関連リンク

参考にした情報源リンク

  • Go言語のexportキーワードに関する情報:
    • https://go.dev/doc/effective_go#names
    • https://www.digitalocean.com/community/tutorials/understanding-package-visibility-in-go
  • Go言語のインターフェース構文に関する情報:
    • https://go.dev/blog/interfaces
    • https://www.geeksforgeeks.org/interfaces-in-golang/
  • Go言語のt.FailNow()t.Error()の違いに関する情報:
    • https://go.dev/pkg/testing/#T.FailNow
    • https://ieftimov.com/post/go-test-t-failnow-t-error/
    • https://dev.to/ankur_anand/t-error-vs-t-failnow-in-go-testing-319k
  • Go言語の歴史に関する情報:
    • https://en.wikipedia.org/wiki/Go_(programming_language)
    • https://www.geeksforgeeks.org/history-of-go-programming-language/
    • https://www.techtarget.com/whatis/definition/Go-programming-language