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

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

このコミットは、Go言語の標準ライブラリbytesパッケージ内のCompare関数とEqual関数のセマンティクス、特にnil引数が渡された場合の挙動を明確にし、それに対応するテストを追加するものです。これにより、これらの関数の振る舞いがより予測可能で、ドキュメント化されたものとなります。

コミット

bytes: document Compare/Equal semantics for nil arguments, and add tests.

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

https://github.com/golang/go/commit/85f2d18a726a999b446a16039aa4bef4e8a4e9e9

元コミット内容

commit 85f2d18a726a999b446a16039aa4bef4e8a4e9e9
Author: David Symonds <dsymonds@golang.org>
Date:   Sat Feb 18 17:39:40 2012 +1100

    bytes: document Compare/Equal semantics for nil arguments, and add tests.
    
    R=golang-dev, bradfitz, r, r
    CC=golang-dev
    https://golang.org/cl/5676090
---
 src/pkg/bytes/bytes.go      |  2 ++\
 src/pkg/bytes/bytes_test.go | 41 ++++++++++++++++++++++++-----------------
 2 files changed, 26 insertions(+), 17 deletions(-)

diff --git a/src/pkg/bytes/bytes.go b/src/pkg/bytes/bytes.go
index e94a0ec5c4..7d1426fb41 100644
--- a/src/pkg/bytes/bytes.go
+++ b/src/pkg/bytes/bytes.go
@@ -13,6 +13,7 @@ import (
 
 // Compare returns an integer comparing the two byte arrays lexicographically.
 // The result will be 0 if a==b, -1 if a < b, and +1 if a > b
+// A nil argument is equivalent to an empty slice.
 func Compare(a, b []byte) int {
 	m := len(a)
 	if m > len(b) {
@@ -37,6 +38,7 @@ func Compare(a, b []byte) int {
 
 // Equal returns a boolean reporting whether a == b.
+// A nil argument is equivalent to an empty slice.
 func Equal(a, b []byte) bool
 
 func equalPortable(a, b []byte) bool {
diff --git a/src/pkg/bytes/bytes_test.go b/src/pkg/bytes/bytes_test.go
index 2a1d41b910..000f235176 100644
--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -46,32 +46,39 @@ type BinOpTest struct {
 	i int
 }
 
-var comparetests = []BinOpTest{
-	{"", "", 0},
-	{"a", "", 1},
-	{"", "a", -1},
-	{"abc", "abc", 0},
-	{"ab", "abc", -1},
-	{"abc", "ab", 1},
-	{"x", "ab", 1},
-	{"ab", "x", -1},
-	{"x", "a", 1},
-	{"b", "x", -1},
+var compareTests = []struct {
+	a, b []byte
+	i    int
+}{
+	{[]byte(""), []byte(""), 0},
+	{[]byte("a"), []byte(""), 1},
+	{[]byte(""), []byte("a"), -1},
+	{[]byte("abc"), []byte("abc"), 0},
+	{[]byte("ab"), []byte("abc"), -1},
+	{[]byte("abc"), []byte("ab"), 1},
+	{[]byte("x"), []byte("ab"), 1},
+	{[]byte("ab"), []byte("x"), -1},
+	{[]byte("x"), []byte("a"), 1},
+	{[]byte("b"), []byte("x"), -1},
+	// nil tests
+	{nil, nil, 0},
+	{[]byte(""), nil, 0},
+	{nil, []byte(""), 0},
+	{[]byte("a"), nil, 1},
+	{nil, []byte("a"), -1},
 }
 
 func TestCompare(t *testing.T) {
-	for _, tt := range comparetests {
-		a := []byte(tt.a)
-		b := []byte(tt.b)
-		cmp := Compare(a, b)
+	for _, tt := range compareTests {
+		cmp := Compare(tt.a, tt.b)
 		if cmp != tt.i {
 			t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp)
 		}
-		eql := Equal(a, b)
+		eql := Equal(tt.a, tt.b)
 		if eql != (tt.i == 0) {
 			t.Errorf(`Equal(%q, %q) = %v`, tt.a, tt.b, eql)
 		}
-		eql = EqualPortable(a, b)
+		eql = EqualPortable(tt.a, tt.b)
 		if eql != (tt.i == 0) {
 			t.Errorf(`EqualPortable(%q, %q) = %v`, tt.a, tt.b, eql)
 		}

変更の背景

このコミットの主な背景は、Go言語のbytesパッケージにおけるCompare関数とEqual関数が、nilのバイトスライスを引数として受け取った場合の挙動を明確にすることにあります。Go言語では、nilスライスと空のスライス([]byte{})は異なる概念ですが、多くのGoの関数や操作では、これらを同じように扱うことが慣例となっています。

しかし、bytes.Comparebytes.Equalのような比較関数において、nil引数のセマンティクスが明示的にドキュメント化されていない場合、開発者はその挙動について混乱したり、予期せぬ結果に遭遇したりする可能性があります。このコミットは、この曖昧さを解消し、nil引数が空のスライスと同等に扱われることを明示的にドキュメントに追加し、その挙動を検証するためのテストケースを拡充することで、ライブラリの堅牢性と使いやすさを向上させることを目的としています。

前提知識の解説

Go言語におけるnilスライスと空スライス

Go言語において、スライスは基盤となる配列への参照、長さ、容量を持つデータ構造です。

  • nilスライス: var s []byteのように宣言されたスライスは、初期値としてnilになります。nilスライスは基盤となる配列への参照を持たず、長さも容量も0です。nilスライスは有効なスライスであり、多くの操作(len(s)cap(s)append(s, ...)など)で問題なく使用できます。
  • 空スライス: []byte{}make([]byte, 0)のように作成されたスライスは、空スライスと呼ばれます。これらは基盤となる配列への参照を持つことがありますが、長さは0です。

Goの慣例として、多くの関数はnilスライスと空スライスを同じように扱います。例えば、json.Marshalnilスライスと空スライスの両方を[]としてJSONにエンコードします。このコミットは、bytesパッケージの比較関数もこの慣例に従うことを明示するものです。

bytes.Compare関数

bytes.Compare(a, b []byte) intは、2つのバイトスライスabを辞書順に比較し、以下のいずれかの整数を返します。

  • 0: abが等しい場合
  • -1: abより小さい場合
  • +1: abより大きい場合

「辞書順」とは、文字列の比較と同様に、バイト列の各要素を先頭から順に比較していく方法です。

bytes.Equal関数

bytes.Equal(a, b []byte) boolは、2つのバイトスライスabが等しいかどうかを報告するブール値を返します。これはbytes.Compare0を返すことと同義です。

技術的詳細

このコミットは、主に以下の2つの側面で変更を加えています。

  1. ドキュメントの追加: src/pkg/bytes/bytes.goファイルにおいて、Compare関数とEqual関数のコメントに「A nil argument is equivalent to an empty slice.」(nil引数は空のスライスと同等である)という記述が追加されました。これにより、これらの関数にnilスライスが渡された場合の挙動が公式に明文化され、開発者が混乱する可能性が低減されます。

  2. テストケースの拡充: src/pkg/bytes/bytes_test.goファイルにおいて、compareTestsというテストデータ構造が変更され、nilスライスを引数として含む新しいテストケースが追加されました。

    • 以前はBinOpTestという構造体を使用し、文字列リテラルを[]byteに変換してテストしていましたが、新しいcompareTestsは直接[]byte型のフィールドを持つ匿名構造体のスライスとして定義されています。これにより、nilスライスを直接テストデータとして指定できるようになりました。
    • 追加されたnil関連のテストケースは以下の通りです。
      • {nil, nil, 0}: 両方nilの場合、等しい(0を返す)。
      • {[]byte(""), nil, 0}: 空スライスとnilの場合、等しい(0を返す)。
      • {nil, []byte(""), 0}: nilと空スライスの場合、等しい(0を返す)。
      • {[]byte("a"), nil, 1}: 非空スライスとnilの場合、非空スライスの方が大きい(1を返す)。
      • {nil, []byte("a"), -1}: nilと非空スライスの場合、nilの方が小さい(-1を返す)。

これらのテストケースの追加により、CompareおよびEqual関数がnil引数を空スライスとして正しく処理し、期待される結果を返すことが保証されます。また、テストループ内でa := []byte(tt.a)b := []byte(tt.b)といった文字列からバイトスライスへの変換が不要になり、テストコードがより直接的になりました。

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

src/pkg/bytes/bytes.go

--- a/src/pkg/bytes/bytes.go
+++ b/src/pkg/bytes/bytes.go
@@ -13,6 +13,7 @@ import (
 
 // Compare returns an integer comparing the two byte arrays lexicographically.
 // The result will be 0 if a==b, -1 if a < b, and +1 if a > b
+// A nil argument is equivalent to an empty slice.
 func Compare(a, b []byte) int {
 	m := len(a)
 	if m > len(b) {
@@ -37,6 +38,7 @@ func Compare(a, b []byte) int {
 
 // Equal returns a boolean reporting whether a == b.
+// A nil argument is equivalent to an empty slice.
 func Equal(a, b []byte) bool
 
 func equalPortable(a, b []byte) bool {

src/pkg/bytes/bytes_test.go

--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -46,32 +46,39 @@ type BinOpTest struct {
 	i int
 }
 
-var comparetests = []BinOpTest{
-	{"", "", 0},
-	{"a", "", 1},
-	{"", "a", -1},
-	{"abc", "abc", 0},
-	{"ab", "abc", -1},
-	{"abc", "ab", 1},
-	{"x", "ab", 1},
-	{"ab", "x", -1},
-	{"x", "a", 1},
-	{"b", "x", -1},
+var compareTests = []struct {
+	a, b []byte
+	i    int
+}{
+	{[]byte(""), []byte(""), 0},
+	{[]byte("a"), []byte(""), 1},
+	{[]byte(""), []byte("a"), -1},
+	{[]byte("abc"), []byte("abc"), 0},
+	{[]byte("ab"), []byte("abc"), -1},
+	{[]byte("abc"), []byte("ab"), 1},
+	{[]byte("x"), []byte("ab"), 1},
+	{[]byte("ab"), []byte("x"), -1},
+	{[]byte("x"), []byte("a"), 1},
+	{[]byte("b"), []byte("x"), -1},
+	// nil tests
+	{nil, nil, 0},
+	{[]byte(""), nil, 0},
+	{nil, []byte(""), 0},
+	{[]byte("a"), nil, 1},
+	{nil, []byte("a"), -1},
 }
 
 func TestCompare(t *testing.T) {
-	for _, tt := range comparetests {
-		a := []byte(tt.a)
-		b := []byte(tt.b)
-		cmp := Compare(a, b)
+	for _, tt := range compareTests {
+		cmp := Compare(tt.a, tt.b)
 		if cmp != tt.i {
 			t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp)
 		}
-		eql := Equal(a, b)
+		eql := Equal(tt.a, tt.b)
 		if eql != (tt.i == 0) {
 			t.Errorf(`Equal(%q, %q) = %v`, tt.a, tt.b, eql)
 		}
-		eql = EqualPortable(a, b)
+		eql = EqualPortable(tt.a, tt.b)
 		if eql != (tt.i == 0) {
 			t.Errorf(`EqualPortable(%q, %q) = %v`, tt.a, tt.b, eql)
 		}

コアとなるコードの解説

src/pkg/bytes/bytes.goの変更

Compare関数とEqual関数のドキュメントコメントに、A nil argument is equivalent to an empty slice.という一文が追加されました。これは、これらの関数がnilバイトスライスを空のバイトスライス(長さ0)として扱うことを明示しています。この変更自体は関数のロジックには影響を与えませんが、APIの振る舞いを明確にし、開発者がnilスライスを安全に渡せることを保証します。Go言語の設計哲学では、ドキュメントによる明確化が非常に重要視されます。

src/pkg/bytes/bytes_test.goの変更

  1. テストデータ構造の変更:

    • 以前はBinOpTestというカスタム構造体を使用していましたが、新しいcompareTestsは匿名構造体のスライスとして定義されています。これにより、テストケースの定義がより簡潔になり、特に[]byte型を直接フィールドに持つことで、nilスライスをテストデータとして直接指定できるようになりました。
    • 古い定義では文字列リテラルを[]byteに変換していましたが、新しい定義では直接[]byte("")のようにバイトスライスリテラルを使用しています。
  2. nilテストケースの追加:

    • // nil testsというコメントの下に、nilスライスを含む5つの新しいテストケースが追加されました。これらのテストは、nilスライスが空スライスと同等に扱われるという新しいドキュメントの記述を検証します。
    • 具体的には、nilnilnilと空スライス、空スライスとnilが等しいと判断されること、そして非空スライスとnilの比較が期待通りに機能することを確認します。
  3. テストループの簡素化:

    • TestCompare関数内のループで、以前はa := []byte(tt.a)b := []byte(tt.b)のように文字列からバイトスライスへの変換を行っていましたが、新しいテストデータ構造ではtt.att.bが既に[]byte型であるため、これらの変換が不要になりました。これにより、テストコードがより直接的で効率的になっています。

これらの変更は、bytesパッケージの堅牢性を高め、nilスライスを扱う際の予期せぬ挙動を防ぎ、開発者にとってより信頼性の高いAPIを提供することに貢献しています。

関連リンク

参考にした情報源リンク