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

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

このコミットは、Go言語の unicode/utf16 パッケージが unicode パッケージへの直接的な依存関係を削除することを目的としています。具体的には、unicode.ReplacementCharunicode.MaxRune という定数を utf16 パッケージ内にコピーし、それらを参照するようにコードを変更しています。これにより、utf16 パッケージの独立性が高まり、unicode パッケージの変更が直接 utf16 に影響を与えるリスクが軽減されます。また、この変更に伴い、コピーされた定数が正しいことを検証するためのテストが追加され、テストファイルの配置も最適化されています。

コミット

commit 98c1baff6f51a2900f38b35e52f6519730c604c4
Author: Rob Pike <r@golang.org>
Date:   Tue Mar 6 14:58:08 2012 +1100

    unicode/utf16: delete dependence on package unicode
    In the test, verify the copied constants are correct.
    Also put the test into package utf16 rather than utf16_test;
    the old location was probably due creating the test from
    utf8, but the separation is not needed here.
    
    R=golang-dev, bradfitz, rsc, rsc, r
    CC=golang-dev
    https://golang.org/cl/5752047

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

https://github.com/golang/go/commit/98c1baff6f51a2900f38b35e52f6519730c604c4

元コミット内容

このコミットの主な内容は以下の通りです。

  • unicode/utf16 パッケージから unicode パッケージへの依存関係を削除する。
  • テストにおいて、コピーされた定数(ReplacementCharMaxRune)が正しいことを検証する。
  • テストコードを utf16_test パッケージから utf16 パッケージ内に移動する。これは、元々 utf8 パッケージのテストから派生したため分離されていたが、utf16 の場合はその分離が不要であると判断されたため。

変更の背景

Go言語のパッケージ設計において、各パッケージは可能な限り独立性を保つことが推奨されます。これは、パッケージ間の結合度を低く保ち、一方のパッケージの変更が予期せず他方のパッケージに影響を与える「連鎖的な変更」のリスクを減らすためです。

unicode/utf16 パッケージは、UTF-16エンコーディングとデコーディングを扱うためのパッケージです。これまで、このパッケージはUnicodeの置換文字(ReplacementChar)や最大有効Unicodeコードポイント(MaxRune)といった定数を unicode パッケージから直接インポートして使用していました。

この依存関係は、unicode パッケージのこれらの定数が変更された場合に、utf16 パッケージも再コンパイルまたは更新が必要になる可能性を意味します。また、utf16 パッケージが unicode パッケージの特定のバージョンに強く結合されることにもなります。

このコミットの背景には、このような不必要な依存関係を解消し、utf16 パッケージの自己完結性を高めるという設計思想があります。ReplacementCharMaxRune は、UTF-16の処理において基本的な定数であり、その値はUnicode標準によって固定されています。したがって、これらの定数を utf16 パッケージ内に直接定義しても、将来的に値が変更される可能性は極めて低く、unicode パッケージへの依存を維持するメリットよりも、独立性を高めるメリットの方が大きいと判断されたと考えられます。

また、テストコードの配置変更も、パッケージの構造とテストの整合性を高めるためのものです。テスト対象のパッケージと同じパッケージ内にテストを配置することで、テストがパッケージの内部要素(エクスポートされていない定数や関数など)にアクセスできるようになり、より包括的なテストが可能になります。

前提知識の解説

UTF-16

UTF-16 (Unicode Transformation Format, 16-bit) は、Unicode文字をエンコードするための可変長文字エンコーディングです。各文字は1つまたは2つの16ビットのコードユニットで表現されます。

  • 基本多言語面 (BMP): U+0000からU+FFFFまでの文字は、1つの16ビットコードユニットで直接表現されます。
  • サロゲートペア: U+10000からU+10FFFFまでの文字(補助面)は、2つの16ビットコードユニットのペア(サロゲートペア)で表現されます。サロゲートペアは、上位サロゲート(U+D800からU+DBFF)と下位サロゲート(U+DC00からU+DFFF)の組み合わせで構成されます。

Unicode Replacement Character (U+FFFD)

Unicode Replacement Character (U+FFFD) は、文字エンコーディングの変換中に、有効な文字にマッピングできないデータが検出された場合に使用される特殊な文字です。これは、不正なバイトシーケンスや、ターゲットエンコーディングで表現できない文字を処理する際に、エラーを示すために挿入されます。Go言語の unicode パッケージでは、この文字が unicode.ReplacementChar として定義されています。

MaxRune (U+10FFFF)

MaxRune は、Unicodeで定義されている最大の有効なコードポイント(文字の数値)です。現在のUnicode標準では、U+0000からU+10FFFFまでの範囲が有効なコードポイントとされています。Go言語の unicode パッケージでは、この値が unicode.MaxRune として定義されています。

Go言語のパッケージと依存関係

Go言語では、コードは「パッケージ」という単位で整理されます。パッケージは、関連する機能の集合であり、他のパッケージからインポートして利用することができます。

  • インポート: import "path/to/package" のように記述することで、他のパッケージの公開された(大文字で始まる)識別子を利用できます。
  • 依存関係: あるパッケージが別のパッケージの機能を利用する場合、そのパッケージは利用されるパッケージに「依存」していると言えます。
  • 結合度: パッケージ間の依存関係が少ないほど、結合度が低いと言われ、これはソフトウェア設計において望ましい特性です。結合度が低いと、一方のパッケージの変更が他方に与える影響が少なくなり、コードの保守性や再利用性が向上します。

このコミットは、utf16 パッケージの unicode パッケージへの結合度を下げることを目的としています。

技術的詳細

このコミットの技術的な変更は、主に以下の3つの側面に焦点を当てています。

  1. 定数のコピーと依存関係の削除:

    • src/pkg/unicode/utf16/utf16.go ファイルにおいて、unicode パッケージのインポートが削除されました。
    • 代わりに、replacementCharmaxRune という2つの定数が utf16.go 内に直接定義されました。これらの値は、それぞれ '\uFFFD''\U0010FFFF' であり、unicode.ReplacementCharunicode.MaxRune の値と同一です。
    • これにより、DecodeRune, EncodeRune, Encode, Decode といった関数内で unicode.ReplacementCharunicode.MaxRune を参照していた箇所が、新しく定義されたローカルな replacementCharmaxRune を参照するように変更されました。
  2. コピーされた定数の検証テストの追加:

    • src/pkg/unicode/utf16/export_test.go という新しいファイルが作成されました。このファイルは、utf16 パッケージのテストのために、内部の定数 maxRunereplacementChar をエクスポートする役割を担います。具体的には、MaxRuneReplacementChar というエクスポートされた定数が、それぞれ内部の maxRunereplacementChar を参照するように定義されています。
    • src/pkg/unicode/utf16/utf16_test.go ファイルに TestConstants という新しいテスト関数が追加されました。このテスト関数は、export_test.go を通じてエクスポートされた MaxRuneReplacementChar の値が、unicode パッケージの unicode.MaxRuneunicode.ReplacementChar の値と一致することを検証します。これにより、utf16 パッケージ内にコピーされた定数が、常に unicode パッケージの元の値と同期していることが保証されます。
  3. テストファイルの配置変更:

    • src/pkg/unicode/utf16/utf16_test.go ファイルは、元々 utf16_test という別のパッケージに属していた可能性があります(コミットメッセージの「put the test into package utf16 rather than utf16_test」という記述から推測)。この変更により、utf16_test.gopackage utf16 に属するように変更され、utf16 パッケージの内部要素にアクセスできるようになりました。
    • src/pkg/unicode/utf8/utf8_test.go にも同様の TestConstants 関数が追加されていますが、これは utf8 パッケージが unicode パッケージの定数をコピーしている場合に、その整合性を検証するためのものです。このコミットは utf16 に焦点を当てていますが、関連する utf8 パッケージにも同様のテストが適用されたことを示唆しています。

これらの変更により、utf16 パッケージは unicode パッケージから独立しつつも、その基本的な定数の値の整合性はテストによって保証されるという、堅牢な設計が実現されています。

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

src/pkg/unicode/utf16/export_test.go (新規ファイル)

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package utf16

// Extra names for constants so we can validate them during testing.
const (
	MaxRune         = maxRune
	ReplacementChar = replacementChar
)

src/pkg/unicode/utf16/utf16.go

--- a/src/pkg/unicode/utf16/utf16.go
+++ b/src/pkg/unicode/utf16/utf16.go
@@ -5,7 +5,14 @@
 // Package utf16 implements encoding and decoding of UTF-16 sequences.
 package utf16
 
-import "unicode"
+// The conditions replacementChar==unicode.ReplacementChar and
+// maxRune==unicode.MaxRune are verified in the tests.
+// Defining them locally avoids this package depending on package unicode.
+
+const (
+	replacementChar = '\uFFFD'     // Unicode replacement character
+	maxRune         = '\U0010FFFF' // Maximum valid Unicode code point.
+)
 
 const (
 	// 0xd800-0xdc00 encodes the high 10 bits of a pair.
@@ -31,15 +38,15 @@ func DecodeRune(r1, r2 rune) rune {
 	if surr1 <= r1 && r1 < surr2 && surr2 <= r2 && r2 < surr3 {
 		return (rune(r1)-surr1)<<10 | (rune(r2) - surr2) + 0x10000
 	}
-	return unicode.ReplacementChar
+	return replacementChar
 }
 
 // EncodeRune returns the UTF-16 surrogate pair r1, r2 for the given rune.
 // If the rune is not a valid Unicode code point or does not need encoding,
 // EncodeRune returns U+FFFD, U+FFFD.
 func EncodeRune(r rune) (r1, r2 rune) {
-	if r < surrSelf || r > unicode.MaxRune || IsSurrogate(r) {
-		return unicode.ReplacementChar, unicode.ReplacementChar
+	if r < surrSelf || r > maxRune || IsSurrogate(r) {
+		return replacementChar, replacementChar
 	}
 	r -= surrSelf
 	return surr1 + (r>>10)&0x3ff, surr2 + r&0x3ff
@@ -58,8 +65,8 @@ func Encode(s []rune) []uint16 {
 	n = 0
 	for _, v := range s {
 		switch {
-		case v < 0, surr1 <= v && v < surr3, v > unicode.MaxRune:
-			v = unicode.ReplacementChar
+		case v < 0, surr1 <= v && v < surr3, v > maxRune:
+			v = replacementChar
 			fallthrough
 		case v < surrSelf:
 			a[n] = uint16(v)
@@ -89,7 +96,7 @@ func Decode(s []uint16) []rune {
 		case surr1 <= r && r < surr3:
 			// invalid surrogate sequence
-			a[n] = unicode.ReplacementChar
+			a[n] = replacementChar
 			n++
 		default:
 			// normal rune

src/pkg/unicode/utf16/utf16_test.go

--- a/src/pkg/unicode/utf16/utf16_test.go
+++ b/src/pkg/unicode/utf16/utf16_test.go
@@ -11,6 +11,16 @@ import (
 	. "unicode/utf16"
 )
 
+// Validate the constants redefined from unicode.
+func TestConstants(t *testing.T) {
+	if MaxRune != unicode.MaxRune {
+		t.Errorf("utf16.maxRune is wrong: %x should be %x", MaxRune, unicode.MaxRune)
+	}
+	if ReplacementChar != unicode.ReplacementChar {
+		t.Errorf("utf16.replacementChar is wrong: %x should be %x", ReplacementChar, unicode.ReplacementChar)
+	}
+}
+
 type encodeTest struct {
 	in  []rune
 	out []uint16

src/pkg/unicode/utf8/utf8_test.go

--- a/src/pkg/unicode/utf8/utf8_test.go
+++ b/src/pkg/unicode/utf8/utf8_test.go
@@ -21,6 +21,16 @@ func init() {
 	}
 }
 
+// Validate the constants redefined from unicode.
+func TestConstants(t *testing.T) {
+	if MaxRune != unicode.MaxRune {
+		t.Errorf("utf8.MaxRune is wrong: %x should be %x", MaxRune, unicode.MaxRune)
+	}
+	if RuneError != unicode.ReplacementChar {
+		t.Errorf("utf8.RuneError is wrong: %x should be %x", RuneError, unicode.ReplacementChar)
+	}
+}
+
 type Utf8Map struct {
 	r   rune
 	str string

コアとなるコードの解説

src/pkg/unicode/utf16/export_test.go

このファイルは、Go言語のテストメカニズムにおける特別な慣習を利用しています。_test.go で終わるファイルはテストコードとして扱われますが、_test サフィックスのないパッケージ名(この場合は package utf16)を持つことで、テスト対象のパッケージと同じパッケージに属していると見なされます。これにより、テストコードはテスト対象パッケージの内部(エクスポートされていない)識別子にアクセスできます。

export_test.go の目的は、utf16.go 内で定義されたエクスポートされていない定数 maxRunereplacementChar を、テストコードからアクセスできるようにエクスポートすることです。MaxRuneReplacementChar というエクスポートされた定数(大文字で始まるため外部からアクセス可能)が、それぞれ内部の maxRunereplacementChar の値を参照するように定義されています。これにより、utf16_test.go 内のテスト関数がこれらの内部定数の値にアクセスし、検証することが可能になります。

src/pkg/unicode/utf16/utf16.go

  • import "unicode" の削除: 最も重要な変更点です。これにより、utf16 パッケージは unicode パッケージへの直接的な依存関係を解消しました。
  • replacementCharmaxRune のローカル定義:
    const (
    	replacementChar = '\uFFFD'     // Unicode replacement character
    	maxRune         = '\U0010FFFF' // Maximum valid Unicode code point.
    )
    
    unicode.ReplacementCharunicode.MaxRune の値が、utf16 パッケージ内で replacementCharmaxRune として再定義されました。これにより、これらの定数を使用する際に unicode パッケージをインポートする必要がなくなりました。
  • 定数参照の変更: DecodeRune, EncodeRune, Encode, Decode 関数内で、これまで unicode.ReplacementCharunicode.MaxRune を参照していた箇所が、新しく定義されたローカルな replacementCharmaxRune を参照するように変更されました。例えば、return unicode.ReplacementCharreturn replacementChar に、r > unicode.MaxRuner > maxRune に変更されています。

src/pkg/unicode/utf16/utf16_test.go

  • TestConstants 関数の追加:
    func TestConstants(t *testing.T) {
    	if MaxRune != unicode.MaxRune {
    		t.Errorf("utf16.maxRune is wrong: %x should be %x", MaxRune, unicode.MaxRune)
    	}
    	if ReplacementChar != unicode.ReplacementChar {
    		t.Errorf("utf16.replacementChar is wrong: %x should be %x", ReplacementChar, unicode.ReplacementChar)
    	}
    }
    
    このテスト関数は、export_test.go を介してエクスポートされた MaxRuneReplacementChar の値が、unicode パッケージの対応する定数(unicode.MaxRuneunicode.ReplacementChar)と一致するかどうかを検証します。これにより、utf16 パッケージが独自に定義した定数が、Unicode標準の正しい値と同期していることが保証されます。これは、依存関係を削除しつつも、値の正確性を維持するための重要な安全策です。

src/pkg/unicode/utf8/utf8_test.go

  • TestConstants 関数の追加: utf8 パッケージのテストファイルにも同様の TestConstants 関数が追加されています。これは、utf8 パッケージも unicode パッケージの定数を内部にコピーしている場合に、その整合性を検証するためのものです。このコミットは utf16 に焦点を当てていますが、Goの標準ライブラリ全体で同様の依存関係解消とテスト戦略が適用されていることを示唆しています。

これらの変更は、Go言語の標準ライブラリにおけるパッケージ設計の原則、特に結合度の低減と自己完結性の向上を反映しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev)
  • Wikipedia (UTF-16)
  • コミットメッセージと差分 (GitHub)
  • Go言語のパッケージ設計に関する一般的な知識