[インデックス 12409] ファイルの概要
このコミットは、Go言語の unicode/utf16
パッケージが unicode
パッケージへの直接的な依存関係を削除することを目的としています。具体的には、unicode.ReplacementChar
と unicode.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
パッケージへの依存関係を削除する。- テストにおいて、コピーされた定数(
ReplacementChar
とMaxRune
)が正しいことを検証する。 - テストコードを
utf16_test
パッケージからutf16
パッケージ内に移動する。これは、元々utf8
パッケージのテストから派生したため分離されていたが、utf16
の場合はその分離が不要であると判断されたため。
変更の背景
Go言語のパッケージ設計において、各パッケージは可能な限り独立性を保つことが推奨されます。これは、パッケージ間の結合度を低く保ち、一方のパッケージの変更が予期せず他方のパッケージに影響を与える「連鎖的な変更」のリスクを減らすためです。
unicode/utf16
パッケージは、UTF-16エンコーディングとデコーディングを扱うためのパッケージです。これまで、このパッケージはUnicodeの置換文字(ReplacementChar
)や最大有効Unicodeコードポイント(MaxRune
)といった定数を unicode
パッケージから直接インポートして使用していました。
この依存関係は、unicode
パッケージのこれらの定数が変更された場合に、utf16
パッケージも再コンパイルまたは更新が必要になる可能性を意味します。また、utf16
パッケージが unicode
パッケージの特定のバージョンに強く結合されることにもなります。
このコミットの背景には、このような不必要な依存関係を解消し、utf16
パッケージの自己完結性を高めるという設計思想があります。ReplacementChar
と MaxRune
は、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つの側面に焦点を当てています。
-
定数のコピーと依存関係の削除:
src/pkg/unicode/utf16/utf16.go
ファイルにおいて、unicode
パッケージのインポートが削除されました。- 代わりに、
replacementChar
とmaxRune
という2つの定数がutf16.go
内に直接定義されました。これらの値は、それぞれ'\uFFFD'
と'\U0010FFFF'
であり、unicode.ReplacementChar
とunicode.MaxRune
の値と同一です。 - これにより、
DecodeRune
,EncodeRune
,Encode
,Decode
といった関数内でunicode.ReplacementChar
やunicode.MaxRune
を参照していた箇所が、新しく定義されたローカルなreplacementChar
やmaxRune
を参照するように変更されました。
-
コピーされた定数の検証テストの追加:
src/pkg/unicode/utf16/export_test.go
という新しいファイルが作成されました。このファイルは、utf16
パッケージのテストのために、内部の定数maxRune
とreplacementChar
をエクスポートする役割を担います。具体的には、MaxRune
とReplacementChar
というエクスポートされた定数が、それぞれ内部のmaxRune
とreplacementChar
を参照するように定義されています。src/pkg/unicode/utf16/utf16_test.go
ファイルにTestConstants
という新しいテスト関数が追加されました。このテスト関数は、export_test.go
を通じてエクスポートされたMaxRune
とReplacementChar
の値が、unicode
パッケージのunicode.MaxRune
とunicode.ReplacementChar
の値と一致することを検証します。これにより、utf16
パッケージ内にコピーされた定数が、常にunicode
パッケージの元の値と同期していることが保証されます。
-
テストファイルの配置変更:
src/pkg/unicode/utf16/utf16_test.go
ファイルは、元々utf16_test
という別のパッケージに属していた可能性があります(コミットメッセージの「put the test into package utf16 rather than utf16_test」という記述から推測)。この変更により、utf16_test.go
はpackage 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
内で定義されたエクスポートされていない定数 maxRune
と replacementChar
を、テストコードからアクセスできるようにエクスポートすることです。MaxRune
と ReplacementChar
というエクスポートされた定数(大文字で始まるため外部からアクセス可能)が、それぞれ内部の maxRune
と replacementChar
の値を参照するように定義されています。これにより、utf16_test.go
内のテスト関数がこれらの内部定数の値にアクセスし、検証することが可能になります。
src/pkg/unicode/utf16/utf16.go
import "unicode"
の削除: 最も重要な変更点です。これにより、utf16
パッケージはunicode
パッケージへの直接的な依存関係を解消しました。replacementChar
とmaxRune
のローカル定義:const ( replacementChar = '\uFFFD' // Unicode replacement character maxRune = '\U0010FFFF' // Maximum valid Unicode code point. )
unicode.ReplacementChar
とunicode.MaxRune
の値が、utf16
パッケージ内でreplacementChar
とmaxRune
として再定義されました。これにより、これらの定数を使用する際にunicode
パッケージをインポートする必要がなくなりました。- 定数参照の変更:
DecodeRune
,EncodeRune
,Encode
,Decode
関数内で、これまでunicode.ReplacementChar
やunicode.MaxRune
を参照していた箇所が、新しく定義されたローカルなreplacementChar
やmaxRune
を参照するように変更されました。例えば、return unicode.ReplacementChar
はreturn replacementChar
に、r > unicode.MaxRune
はr > 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
を介してエクスポートされたMaxRune
とReplacementChar
の値が、unicode
パッケージの対応する定数(unicode.MaxRune
とunicode.ReplacementChar
)と一致するかどうかを検証します。これにより、utf16
パッケージが独自に定義した定数が、Unicode標準の正しい値と同期していることが保証されます。これは、依存関係を削除しつつも、値の正確性を維持するための重要な安全策です。
src/pkg/unicode/utf8/utf8_test.go
TestConstants
関数の追加:utf8
パッケージのテストファイルにも同様のTestConstants
関数が追加されています。これは、utf8
パッケージもunicode
パッケージの定数を内部にコピーしている場合に、その整合性を検証するためのものです。このコミットはutf16
に焦点を当てていますが、Goの標準ライブラリ全体で同様の依存関係解消とテスト戦略が適用されていることを示唆しています。
これらの変更は、Go言語の標準ライブラリにおけるパッケージ設計の原則、特に結合度の低減と自己完結性の向上を反映しています。
関連リンク
- Go言語の
unicode
パッケージ: https://pkg.go.dev/unicode - Go言語の
unicode/utf16
パッケージ: https://pkg.go.dev/unicode/utf16 - Go言語の
unicode/utf8
パッケージ: https://pkg.go.dev/unicode/utf8 - Unicode Consortium: https://www.unicode.org/
- UTF-16: https://en.wikipedia.org/wiki/UTF-16
参考にした情報源リンク
- Go言語の公式ドキュメント (pkg.go.dev)
- Wikipedia (UTF-16)
- コミットメッセージと差分 (GitHub)
- Go言語のパッケージ設計に関する一般的な知識