[インデックス 11667] ファイルの概要
このコミットは、Go言語の標準ライブラリであるgo/token
パッケージから、encoding/gob
パッケージへの直接的な依存関係を削除することを目的としています。これにより、go/token
パッケージのシリアライズ/デシリアライズ機能がより汎用的になり、特定のシリアライズ形式に縛られずに利用できるようになります。具体的には、FileSet
構造体の読み書きメソッドが、具体的なio.Reader
やio.Writer
ではなく、シリアライズ/デシリアライズのロジックをカプセル化した関数を受け取るように変更されています。
コミット
- コミットハッシュ:
668418d1227aeb01782ba0ee05ac4ba657c0b5a2
- 作者: Robert Griesemer gri@golang.org
- コミット日時: 2012年2月6日 月曜日 17:41:19 -0800
- コミットメッセージ:
go/token: remove dependency on encoding/gob R=dsymonds CC=golang-dev https://golang.org/cl/5636053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/668418d1227aeb01782ba0ee05ac4ba657c0b5a2
元コミット内容
commit 668418d1227aeb01782ba0ee05ac4ba657c0b5a2
Author: Robert Griesemer <gri@golang.org>
Date: Mon Feb 6 17:41:19 2012 -0800
go/token: remove dependency on encoding/gob
R=dsymonds
CC=golang-dev
https://golang.org/cl/5636053
---
src/cmd/godoc/index.go | 10 ++++++++--
src/pkg/go/token/serialize.go | 26 ++++++--------------------\
src/pkg/go/token/serialize_test.go | 11 +++++++++--
3 files changed, 23 insertions(+), 24 deletions(-)
diff --git a/src/cmd/godoc/index.go b/src/cmd/godoc/index.go
index daf1bc2cc1..f5b531b054 100644
--- a/src/cmd/godoc/index.go
+++ b/src/cmd/godoc/index.go
@@ -867,7 +867,10 @@ func (x *Index) Write(w io.Writer) error {
return err
}
if fulltext {
- if err := x.fset.Write(w); err != nil {
+ encode := func(x interface{}) error {
+ return gob.NewEncoder(w).Encode(x)
+ }
+ if err := x.fset.Write(encode); err != nil {
return err
}
if err := x.suffixes.Write(w); err != nil {
@@ -897,7 +900,10 @@ func (x *Index) Read(r io.Reader) error {
return err
}
x.suffixes = new(suffixarray.Index)
- if err := x.suffixes.Read(r); err != nil {
+ decode := func(x interface{}) error {
+ return gob.NewDecoder(r).Decode(x)
+ }
+ if err := x.suffixes.Read(decode); err != nil {
return err
}
}
diff --git a/src/pkg/go/token/serialize.go b/src/pkg/go/token/serialize.go
index 042d6abdf9..4adc8f9e33 100644
--- a/src/pkg/go/token/serialize.go
+++ b/src/pkg/go/token/serialize.go
@@ -4,11 +4,6 @@
package token
-import (
- "encoding/gob"
- "io"
-)
-
type serializedFile struct {
// fields correspond 1:1 to fields with same (lower-case) name in File
Name string
@@ -23,19 +18,10 @@ type serializedFileSet struct {
Files []serializedFile
}
-func (s *serializedFileSet) Read(r io.Reader) error {
- return gob.NewDecoder(r).Decode(s)
-}
-
-func (s *serializedFileSet) Write(w io.Writer) error {
- return gob.NewEncoder(w).Encode(s)
-}
-
-// Read reads the fileset from r into s; s must not be nil.\n// If r does not also implement io.ByteReader, it will be wrapped in a bufio.Reader.
-func (s *FileSet) Read(r io.Reader) error {\n+// Read calls decode to deserialize a file set into s; s must not be nil.\n+func (s *FileSet) Read(decode func(interface{}) error) error {\n var ss serializedFileSet
-\tif err := ss.Read(r); err != nil {\n+\tif err := decode(&ss); err != nil {\
return err
}
@@ -53,8 +39,8 @@ func (s *FileSet) Read(r io.Reader) error {\
return nil
}
-// Write writes the fileset s to w.\n-func (s *FileSet) Write(w io.Writer) error {\n+// Write calls encode to serialize the file set s.\n+func (s *FileSet) Write(encode func(interface{}) error) error {\
var ss serializedFileSet
s.mutex.Lock()
@@ -66,5 +52,5 @@ func (s *FileSet) Write(w io.Writer) error {\
ss.Files = files
s.mutex.Unlock()
-\treturn ss.Write(w)\n+\treturn encode(ss)\n }\ndiff --git a/src/pkg/go/token/serialize_test.go b/src/pkg/go/token/serialize_test.go
index a8ce30ab2f..4e925adb6f 100644
--- a/src/pkg/go/token/serialize_test.go
+++ b/src/pkg/go/token/serialize_test.go
@@ -6,6 +6,7 @@ package token
import (
"bytes"
+ "encoding/gob"
"fmt"
"testing"
)
@@ -69,12 +70,18 @@ func equal(p, q *FileSet) error {\
func checkSerialize(t *testing.T, p *FileSet) {
var buf bytes.Buffer
-\tif err := p.Write(&buf); err != nil {\n+\tencode := func(x interface{}) error {\n+\t\treturn gob.NewEncoder(&buf).Encode(x)\n+\t}\n+\tif err := p.Write(encode); err != nil {\
t.Errorf("writing fileset failed: %s", err)
return
}
q := NewFileSet()
-\tif err := q.Read(&buf); err != nil {\n+\tdecode := func(x interface{}) error {\n+\t\treturn gob.NewDecoder(&buf).Decode(x)\n+\t}\n+\tif err := q.Read(decode); err != nil {\
t.Errorf("reading fileset failed: %s", err)
return
}
変更の背景
この変更の主な背景は、go/token
パッケージの汎用性と再利用性を高めることにあります。
- 依存関係の疎結合化: 以前の
go/token
パッケージは、FileSet
構造体のシリアライズ/デシリアライズにencoding/gob
パッケージを直接使用していました。これは、go/token
パッケージが特定のシリアライズ形式に強く結合していることを意味します。この結合を解消することで、go/token
パッケージはgob
以外のシリアライズ形式(例: JSON, Protocol Buffers, XML, あるいはカスタムバイナリ形式)を使用したいアプリケーションからも利用しやすくなります。 - 柔軟性の向上:
go/token
パッケージがシリアライズ/デシリアライズの具体的な実装を内部に持たず、外部からそのロジックを注入できるようにすることで、パッケージの利用者は自身の要件に合わせてシリアライズ方法を選択できるようになります。例えば、パフォーマンス要件が厳しい場合や、異なるシステムとの連携のために特定のデータ形式が必要な場合に、柔軟に対応できます。 - パッケージの責務の明確化:
go/token
パッケージの主要な責務は、Goソースコードのトークンと位置情報を管理することです。シリアライズの具体的な方法は、その責務の範囲外と見なすことができます。この変更により、go/token
パッケージは自身のコアな機能に集中し、シリアライズの詳細は呼び出し元に委ねるという、よりクリーンな設計原則に沿うことになります。 - 将来的な拡張性: 特定のシリアライズ形式に依存しないことで、将来的にGo言語や関連ツールが新しいシリアライズ技術を採用した場合でも、
go/token
パッケージのコードを変更することなく対応できるようになります。
前提知識の解説
Go言語
Go(Golang)は、Googleによって開発されたオープンソースのプログラミング言語です。静的型付け、コンパイル型、並行処理のサポート、ガベージコレクションなどの特徴を持ち、シンプルさ、効率性、信頼性を重視して設計されています。システムプログラミング、Webサービス、ネットワークツールなど、幅広い分野で利用されています。
go/token
パッケージ
go/token
は、Go言語の標準ライブラリの一部であり、Goソースコードの字句解析(トークン化)や構文解析において、ソースコード上の位置情報(ファイル名、行番号、列番号、オフセット)を管理するためのパッケージです。
File
: 個々のソースファイルに関する位置情報(ファイル名、サイズ、行オフセットなど)を保持します。FileSet
: 複数のFile
オブジェクトをまとめて管理するコンテナです。これにより、複数のファイルにまたがるソースコード全体での位置情報を一貫して扱うことができます。コンパイラやリンター、コードフォーマッターなど、Goのコードを解析・操作するツールで広く利用されます。
encoding/gob
パッケージ
encoding/gob
は、Go言語の標準ライブラリの一つで、Goのデータ構造をバイナリ形式でシリアライズ(符号化)およびデシリアライズ(復号化)するためのパッケージです。
- シリアライズ: プログラム内のオブジェクト(データ構造)を、ファイルに保存したり、ネットワーク経由で送信したりするために、バイト列の形式に変換するプロセスです。
- デシリアライズ: シリアライズされたバイト列を、元のオブジェクトの形式に復元するプロセスです。
gob
は、Goプログラム間でGoの値を効率的に転送するのに特に適しています。データ型情報も一緒にエンコードされるため、デシリアライズ時に型情報が自動的に利用されます。
依存性の注入 (Dependency Injection)
依存性の注入(DI)は、ソフトウェア設計パターンの一つで、オブジェクトが依存する他のオブジェクト(依存性)を、オブジェクト自身が生成するのではなく、外部から提供(注入)されるようにする手法です。これにより、コンポーネント間の結合度を低減し、コードの再利用性、テスト容易性、保守性を向上させることができます。
今回のコミットでは、go/token
パッケージがencoding/gob
に直接依存する代わりに、シリアライズ/デシリアライズの具体的なロジックを外部から関数として受け取るように変更されています。これは、シリアライズの「依存性」をgo/token
パッケージの外部から「注入」する形にすることで、パッケージの内部実装から特定のシリアライズ形式への結合を排除する、一種の依存性注入と見なすことができます。
技術的詳細
このコミットの技術的な核心は、go/token
パッケージ内のFileSet
構造体のシリアライズ/デシリアライズメカニズムを、特定のencoding/gob
実装から抽象化し、汎用的な関数インターフェースに置き換えた点にあります。
変更前、FileSet
は内部でencoding/gob
を直接利用し、io.Reader
やio.Writer
を介して自身の状態を読み書きしていました。これは、FileSet
がgob
形式でのみシリアライズ/デシリアライズ可能であることを意味し、他の形式での利用を困難にしていました。
変更後、FileSet.Read
およびFileSet.Write
メソッドのシグネチャが以下のように変更されました。
- 変更前:
func (s *FileSet) Read(r io.Reader) error func (s *FileSet) Write(w io.Writer) error
- 変更後:
func (s *FileSet) Read(decode func(interface{}) error) error func (s *FileSet) Write(encode func(interface{}) error) error
新しいシグネチャでは、io.Reader
やio.Writer
の代わりに、func(interface{}) error
型の関数decode
とencode
を引数として受け取ります。
encode func(interface{}) error
: この関数は、任意のGoの値を引数として受け取り、それをバイトストリームにエンコードして書き込む責務を持ちます。decode func(interface{}) error
: この関数は、バイトストリームからデータを読み込み、それを任意のGoの値にデコードして引数に渡されたインターフェースに格納する責務を持ちます。
これにより、go/token
パッケージ自体は、データのエンコード/デコードの具体的な方法を知る必要がなくなりました。その代わりに、呼び出し元が、gob
、json
、xml
、あるいはカスタムのシリアライザなど、任意のシリアライズメカニズムに対応するencode
およびdecode
関数を提供できるようになります。
この変更に伴い、src/cmd/godoc/index.go
では、FileSet
のシリアライズ/デシリアライズに引き続きgob
を使用するために、以下のようなラッパー関数(クロージャ)が導入されました。
// godoc/index.go の変更箇所
// Writeメソッド内
encode := func(x interface{}) error {
return gob.NewEncoder(w).Encode(x)
}
if err := x.fset.Write(encode); err != nil { ... }
// Readメソッド内
decode := func(x interface{}) error {
return gob.NewDecoder(r).Decode(x)
}
if err := x.suffixes.Read(decode); err != nil { ... }
これらのクロージャは、io.Writer
(w
) や io.Reader
(r
) を使用してgob.NewEncoder
やgob.NewDecoder
を初期化し、そのEncode
やDecode
メソッドをFileSet.Write
やFileSet.Read
が期待するfunc(interface{}) error
シグネチャに適合させています。これにより、godoc
コマンドは、go/token
パッケージの変更後も、既存のgob
形式のインデックスファイルを問題なく読み書きできるようになっています。
また、src/pkg/go/token/serialize.go
からは、encoding/gob
とio
のインポートが削除され、serializedFileSet
構造体に対するRead
とWrite
メソッドも削除されました。これは、FileSet
のシリアライズロジックがserializedFileSet
の内部メソッドから、FileSet
自身のメソッドに直接移動し、かつその実装が外部から注入される関数に委ねられるようになったためです。
テストコード(src/pkg/go/token/serialize_test.go
)も、この新しいインターフェースに合わせて修正され、テスト内で明示的にgob.NewEncoder
とgob.NewDecoder
を作成し、そのEncode
/Decode
メソッドをFileSet.Write
/FileSet.Read
に渡す形になっています。これにより、go/token
パッケージのシリアライズ機能が正しく動作することを確認しています。
コアとなるコードの変更箇所
src/pkg/go/token/serialize.go
このファイルは、go/token
パッケージにおけるFileSet
のシリアライズ/デシリアライズロジックを定義しています。
-
インポートの削除:
--- a/src/pkg/go/token/serialize.go +++ b/src/pkg/go/token/serialize.go @@ -4,11 +4,6 @@ package token -import ( - "encoding/gob" - "io" -)
encoding/gob
とio
パッケージへの直接的な依存が削除されました。 -
serializedFileSet
のRead
/Write
メソッドの削除:serializedFileSet
はFileSet
の内部表現をシリアライズ可能にした構造体ですが、これに対するgob
ベースのRead
/Write
メソッドが削除されました。--- a/src/pkg/go/token/serialize.go +++ b/src/pkg/go/token/serialize.go @@ -23,19 +18,10 @@ type serializedFileSet struct { Files []serializedFile } -func (s *serializedFileSet) Read(r io.Reader) error { - return gob.NewDecoder(r).Decode(s) -} - -func (s *serializedFileSet) Write(w io.Writer) error { - return gob.NewEncoder(w).Encode(s) -}
-
FileSet.Read
メソッドのシグネチャ変更と実装修正:io.Reader
を受け取る代わりに、decode func(interface{}) error
関数を受け取るようになりました。--- a/src/pkg/go/token/serialize.go +++ b/src/pkg/go/token/serialize.go @@ -23,19 +18,10 @@ type serializedFileSet struct { Files []serializedFile } -func (s *serializedFileSet) Read(r io.Reader) error { - return gob.NewDecoder(r).Decode(s) -} - -func (s *serializedFileSet) Write(w io.Writer) error { - return gob.NewEncoder(w).Encode(s) -} - -// Read reads the fileset from r into s; s must not be nil.\n// If r does not also implement io.ByteReader, it will be wrapped in a bufio.Reader. -func (s *FileSet) Read(r io.Reader) error {\n+// Read calls decode to deserialize a file set into s; s must not be nil. +func (s *FileSet) Read(decode func(interface{}) error) error { var ss serializedFileSet -\tif err := ss.Read(r); err != nil {\n+\tif err := decode(&ss); err != nil { return err }
-
FileSet.Write
メソッドのシグネチャ変更と実装修正:io.Writer
を受け取る代わりに、encode func(interface{}) error
関数を受け取るようになりました。--- a/src/pkg/go/token/serialize.go +++ b/src/pkg/go/token/serialize.go @@ -53,8 +39,8 @@ func (s *FileSet) Read(r io.Reader) error {\ return nil } -// Write writes the fileset s to w.\n-func (s *FileSet) Write(w io.Writer) error {\n+// Write calls encode to serialize the file set s. +func (s *FileSet) Write(encode func(interface{}) error) error { var ss serializedFileSet s.mutex.Lock() @@ -66,5 +52,5 @@ func (s *FileSet) Write(w io.Writer) error {\ ss.Files = files s.mutex.Unlock() -\treturn ss.Write(w)\n+\treturn encode(ss) }
src/cmd/godoc/index.go
godoc
コマンドはgo/token
パッケージを利用しているため、FileSet
のシリアライズ/デシリアライズ方法の変更に合わせて、このファイルも修正されました。
-
Index.Write
メソッド内の変更:x.fset.Write
を呼び出す際に、gob.NewEncoder
を使用してencode
クロージャを作成し、それを渡すように変更されました。--- a/src/cmd/godoc/index.go +++ b/src/cmd/godoc/index.go @@ -867,7 +867,10 @@ func (x *Index) Write(w io.Writer) error { return err } if fulltext { - if err := x.fset.Write(w); err != nil { + encode := func(x interface{}) error { + return gob.NewEncoder(w).Encode(x) + } + if err := x.fset.Write(encode); err != nil { return err } if err := x.suffixes.Write(w); err != nil {
-
Index.Read
メソッド内の変更:x.suffixes.Read
を呼び出す際に、gob.NewDecoder
を使用してdecode
クロージャを作成し、それを渡すように変更されました。--- a/src/cmd/godoc/index.go +++ b/src/cmd/godoc/index.go @@ -897,7 +900,10 @@ func (x *Index) Read(r io.Reader) error { return err } x.suffixes = new(suffixarray.Index) - if err := x.suffixes.Read(r); err != nil { + decode := func(x interface{}) error { + return gob.NewDecoder(r).Decode(x) + } + if err := x.suffixes.Read(decode); err != nil { return err } }
src/pkg/go/token/serialize_test.go
テストコードも、新しいFileSet.Write
およびFileSet.Read
のシグネチャに合わせて更新されました。
checkSerialize
関数内の変更: テスト内でgob.NewEncoder
とgob.NewDecoder
を明示的に作成し、そのEncode
/Decode
メソッドをFileSet.Write
/FileSet.Read
に渡すように修正されました。--- a/src/pkg/go/token/serialize_test.go +++ b/src/pkg/go/token/serialize_test.go @@ -6,6 +6,7 @@ package token import ( "bytes" + "encoding/gob" "fmt" "testing" ) @@ -69,12 +70,18 @@ func equal(p, q *FileSet) error {\ func checkSerialize(t *testing.T, p *FileSet) { var buf bytes.Buffer -\tif err := p.Write(&buf); err != nil {\n+\tencode := func(x interface{}) error { +\t\treturn gob.NewEncoder(&buf).Encode(x) +\t} +\tif err := p.Write(encode); err != nil { t.Errorf("writing fileset failed: %s", err) return } q := NewFileSet() -\tif err := q.Read(&buf); err != nil {\n+\tdecode := func(x interface{}) error { +\t\treturn gob.NewDecoder(&buf).Decode(x) +\t} +\tif err := q.Read(decode); err != nil { t.Errorf("reading fileset failed: %s", err) return }
コアとなるコードの解説
このコミットのコアとなる変更は、go/token
パッケージが特定のシリアライズ形式(この場合はencoding/gob
)に直接依存するのをやめ、シリアライズ/デシリアライズのロジックを外部から注入可能な関数として受け入れるようにした点です。
具体的には、FileSet
構造体のRead
およびWrite
メソッドが、io.Reader
やio.Writer
といったストリームインターフェースを直接扱うのではなく、func(interface{}) error
という関数シグネチャを持つdecode
およびencode
関数を引数として受け取るように変更されました。
- 抽象化の実現: これにより、
go/token
パッケージは、データの具体的なバイト列への変換方法や、バイト列からの復元方法について関知する必要がなくなりました。パッケージの内部ロジックは、単にFileSet
の内部表現(serializedFileSet
)を、提供されたencode
関数を使ってエンコードし、提供されたdecode
関数を使ってデコードするだけになります。 - 柔軟なシリアライズ形式の選択: この設計変更により、
go/token
パッケージの利用者は、自身のアプリケーションの要件に応じて、gob
、json
、xml
、protobuf
など、任意のシリアライズ形式を選択し、それに対応するencode
/decode
関数をFileSet.Write
/FileSet.Read
に渡すことができるようになります。例えば、godoc
コマンドは引き続きgob
を使用していますが、他のツールがFileSet
を異なる形式で保存したい場合でも、go/token
パッケージのコードを変更することなく対応可能です。 - テスト容易性の向上: シリアライズロジックが外部から注入可能になったことで、テスト時にも特定のシリアライズ形式に依存せず、モックやスタブの
encode
/decode
関数を渡して、FileSet
のシリアライズ/デシリアライズ動作をより容易にテストできるようになります。 - 責務の分離:
go/token
パッケージは、Goソースコードのトークンと位置情報を管理するという本来の責務に集中し、データの永続化や転送といったシリアライズの責務は、呼び出し元に委ねられる形になりました。これは、ソフトウェア設計における「単一責任の原則」に合致し、コードベースの健全性を高めます。
この変更は、Go言語の標準ライブラリが、特定の技術や実装に強く依存するのではなく、より汎用的で柔軟なインターフェースを提供する方向性を示していると言えます。
関連リンク
- GitHubでのコミットページ: https://github.com/golang/go/commit/668418d1227aeb01782ba0ee05ac4ba657c0b5a2
- Gerrit Change-ID: https://golang.org/cl/5636053
参考にした情報源リンク
- Go言語公式ドキュメント: https://go.dev/
go/token
パッケージドキュメント: https://pkg.go.dev/go/tokenencoding/gob
パッケージドキュメント: https://pkg.go.dev/encoding/gob- Go言語における依存性注入に関する一般的な情報源 (例: Go Dependency Injection - A Practical Guide など、具体的なURLは省略)