[インデックス 11353] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/xml パッケージのAPIを、他の encoding パッケージ(encoding/json や encoding/gob など)のAPIとより一貫性のあるものにするための大幅な変更を導入しています。この変更には、gofix ツールによる自動コード修正のサポートも含まれています。
コミット
commit 0442087f93d49dec95cd327efbc8c760484ac8bb
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Tue Jan 24 01:10:32 2012 -0200
encoding/xml: bring API closer to other packages
Includes gofix module. The only case not covered should be
xml.Unmarshal, since it remains with a similar interface, and
would require introspecting the type of its first argument
better.
Fixes #2626.
R=golang-dev, rsc, gustavo
CC=golang-dev
https://golang.org/cl/5574053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0442087f93d49dec95cd327efbc8c760484ac8bb
元コミット内容
encoding/xml パッケージのAPIを他のパッケージに近づける。
gofix モジュールを含む。xml.Unmarshal は同様のインターフェースを維持しており、その最初の引数の型をより適切に内省する必要があるため、カバーされていない唯一のケースである。
Issue #2626 を修正。
変更の背景
Go言語の初期段階において、標準ライブラリ内の様々な encoding パッケージ(例: json, gob, xml)は、それぞれ異なるAPI設計を持っていました。これは、開発者が異なる形式のデータを扱う際に、学習コストやコードの一貫性の欠如といった問題を引き起こしていました。特に encoding/xml パッケージは、他のパッケージと比較してストリーム処理の概念が希薄であり、Marshal や Unmarshal 関数が io.Writer や io.Reader を直接引数に取るなど、使い勝手の面で改善の余地がありました。
このコミットは、Go 1のリリースに向けて、標準ライブラリ全体のAPIの一貫性を高めるという大きな目標の一環として行われました。特に、encoding/xml を encoding/json や encoding/gob と同様のパターンに合わせることで、開発者がXMLデータをより直感的に扱えるようにすることを目指しました。
コミットメッセージにある Fixes #2626 については、現在のGitHubリポジトリのIssue #2626は golang/vscode-go のものであり、このコミットの時期(2012年)とは関連がありません。これは、当時のGoプロジェクトが使用していた別のIssueトラッカー(例: Google CodeのIssueトラッカーなど)の番号を指している可能性が高いです。
前提知識の解説
- Go言語の
encodingパッケージ: Go言語の標準ライブラリには、JSON、XML、Gobなどの様々なデータ形式をエンコード(Goのデータ構造から外部形式へ変換)およびデコード(外部形式からGoのデータ構造へ変換)するためのパッケージ群が含まれています。これらのパッケージは、通常、MarshalおよびUnmarshal関数を提供し、それぞれGoの値をバイトスライスに変換したり、バイトスライスをGoの値に変換したりします。 io.Readerとio.Writer: Go言語におけるI/O操作の基本的なインターフェースです。io.Readerはデータの読み込み元を抽象化し、io.Writerはデータの書き込み先を抽象化します。gofixツール:gofixはGo言語の公式ツールの一つで、GoのAPIや言語仕様の変更に合わせて既存のGoソースコードを自動的に修正するために使用されます。これにより、Go言語の進化に伴う破壊的変更があっても、開発者が手動でコードを修正する手間を大幅に削減できます。gofixは、AST(抽象構文木)を操作してコードを変換します。- XMLパーシングのストリーム処理: XMLドキュメントを処理する際、ドキュメント全体をメモリに読み込むのではなく、要素を一つずつ読み込んで処理する方式です。これにより、大きなXMLファイルを効率的に処理できます。
encoding/xmlパッケージでは、Decoderがこのストリーム処理を担います。
技術的詳細
このコミットの主要な変更点は、encoding/xml パッケージのAPIを、他の encoding パッケージ(特に encoding/json)のパターンに合わせることにあります。
-
ParserからDecoderへの名称変更と機能拡張:- 既存の
xml.Parser型がxml.Decoderに名称変更されました。これは、他のencodingパッケージにおけるjson.Decoderやgob.Decoderとの整合性を図るためです。 Decoder型には、新たにDecodeメソッドが追加されました。これにより、NewDecoder(r io.Reader).Decode(v interface{})という形式で、ストリームから直接Goの構造体にデコードできるようになりました。UnmarshalメソッドはDecodeElementに名称変更され、特定の開始要素からデコードする機能を提供します。
- 既存の
-
Marshal関数の変更:- 従来の
func Marshal(w io.Writer, v interface{}) errorからfunc Marshal(v interface{}) ([]byte, error)に変更されました。これにより、encoding/json.Marshalと同様に、Goの値を直接バイトスライスにエンコードするようになりました。 - ストリームへの書き込みが必要な場合は、新たに導入された
Encoder型を使用するNewEncoder(w io.Writer).Encode(v interface{})というパターンに移行しました。
- 従来の
-
Unmarshal関数の変更:- 従来の
func Unmarshal(r io.Reader, val interface{}) errorからfunc Unmarshal(data []byte, v interface{}) errorに変更されました。これにより、encoding/json.Unmarshalと同様に、バイトスライスから直接Goの構造体にデコードするようになりました。 - ストリームからの読み込みが必要な場合は、
NewDecoder(r io.Reader).Decode(v interface{})というパターンに移行しました。
- 従来の
-
gofixツールの統合:- このAPI変更は破壊的であるため、既存のコードベースを新しいAPIに自動的に移行するための
gofixモジュール (src/cmd/gofix/xmlapi.go) が追加されました。 gofixは、xml.Marshal(a, b)をxml.NewEncoder(a).Encode(b)に、xml.NewParser(stream)をxml.NewDecoder(stream)に、p.Unmarshal(v, start)をp.DecodeElement(v, start)に自動的に書き換えます。- ただし、
xml.Unmarshalの変更は、その最初の引数の型を内省する必要があるため、gofixでは完全にカバーされていません。これは、Unmarshalがio.Readerではなく[]byteを受け取るようになったため、io.Readerを渡している既存のコードを自動的にNewDecoderを使用するように変換することが難しいことを意味します。
- このAPI変更は破壊的であるため、既存のコードベースを新しいAPIに自動的に移行するための
-
内部実装の変更:
src/pkg/encoding/xml/xml.go内のParser構造体とその関連メソッドがDecoderにリネームされ、d.Strict,d.AutoClose,d.Entityなどのフィールドアクセスもp.からd.に変更されました。src/pkg/encoding/xml/marshal.goにEncoder型とNewEncoder関数、Encodeメソッドが追加されました。src/pkg/encoding/xml/read.goにUnmarshal関数のシグネチャ変更と、DecoderのDecodeおよびDecodeElementメソッドが追加されました。
これらの変更により、encoding/xml パッケージは、Go言語の他のエンコーディングパッケージとより統一されたインターフェースを提供するようになりました。
コアとなるコードの変更箇所
このコミットは、主に以下のファイルに影響を与えています。
src/pkg/encoding/xml/marshal.go:Marshal関数のシグネチャ変更と、Encoder型、NewEncoder関数、Encodeメソッドの追加。src/pkg/encoding/xml/read.go:Unmarshal関数のシグネチャ変更と、Decoder型のDecodeおよびDecodeElementメソッドの追加。src/pkg/encoding/xml/xml.go:Parser型をDecoderにリネームし、関連するメソッドのレシーバ名も変更。src/cmd/gofix/xmlapi.go:encoding/xmlのAPI変更に対応するためのgofixモジュールを新規追加。src/cmd/gofix/xmlapi_test.go:xmlapi.goのテストコードを新規追加。misc/dashboard/builder/main.go,src/cmd/godoc/codewalk.go,src/pkg/encoding/xml/marshal_test.go,src/pkg/encoding/xml/read_test.go,src/pkg/encoding/xml/xml_test.go: 新しいAPIに合わせて既存のコードとテストを修正。
コアとなるコードの解説
src/pkg/encoding/xml/marshal.go の変更
// 変更前
// func Marshal(w io.Writer, v interface{}) (err error) { ... }
// 変更後
func Marshal(v interface{}) ([]byte, error) {
var b bytes.Buffer
if err := NewEncoder(&b).Encode(v); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// 新規追加
type Encoder struct {
printer
}
// 新規追加
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{printer{bufio.NewWriter(w)}}
}
// 新規追加
func (enc *Encoder) Encode(v interface{}) error {
err := enc.marshalValue(reflect.ValueOf(v), nil)
enc.Flush()
return err
}
Marshal 関数は、io.Writer を引数に取らず、直接 []byte を返すように変更されました。内部的には、bytes.Buffer を使用して NewEncoder と Encode メソッドを呼び出すことで、ストリームへの書き込みロジックを再利用しています。Encoder 型が導入され、NewEncoder で io.Writer を受け取り、Encode メソッドでGoの値をXMLとしてストリームに書き込む責任を持つようになりました。
src/pkg/encoding/xml/read.go の変更
// 変更前
// func Unmarshal(r io.Reader, val interface{}) error { ... }
// 変更後
func Unmarshal(data []byte, v interface{}) error {
return NewDecoder(bytes.NewBuffer(data)).Decode(v)
}
// 新規追加
// Decode works like xml.Unmarshal, except it reads the decoder
// stream to find the start element.
func (d *Decoder) Decode(v interface{}) error {
return d.DecodeElement(v, nil)
}
// 新規追加
// DecodeElement works like xml.Unmarshal except that it takes
// a pointer to the start XML element to decode into v.
// It is useful when a client reads some raw XML tokens itself
// but also wants to defer to Unmarshal for some elements.
func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error {
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return errors.New("non-pointer passed to Unmarshal")
}
return d.unmarshal(val.Elem(), start)
}
// 変更前: func (p *Parser) unmarshal(...)
// 変更後: func (p *Decoder) unmarshal(...)
Unmarshal 関数も io.Reader ではなく []byte を引数に取るように変更されました。内部では bytes.NewBuffer を使って NewDecoder を呼び出し、Decode メソッドに処理を委譲しています。Decoder 型には、Decode と DecodeElement の2つの新しいメソッドが追加され、それぞれストリーム全体または特定の要素からのデコードを可能にしています。また、Parser の unmarshal メソッドのレシーバが Decoder に変更されています。
src/pkg/encoding/xml/xml.go の変更
// 変更前: type Parser struct { ... }
// 変更後: type Decoder struct { ... }
// 変更前: func NewParser(r io.Reader) *Parser { ... }
// 変更後: func NewDecoder(r io.Reader) *Decoder { ... }
// 変更前: func (p *Parser) Token() (t Token, err error) { ... }
// 変更後: func (d *Decoder) Token() (t Token, err error) { ... }
// その他、Parserのフィールドやメソッドのレシーバ名がpからdに変更
このファイルでは、Parser 型が Decoder に完全にリネームされ、それに伴い NewParser 関数も NewDecoder に変更されました。また、Parser のメソッドのレシーバ変数名も p から d に変更され、コード全体で一貫性が保たれています。
src/cmd/gofix/xmlapi.go の新規追加
package main
import (
"go/ast"
)
func init() {
register(xmlapiFix)
}
var xmlapiFix = fix{
"xmlapi",
"2012-01-23",
xmlapi,
`
Make encoding/xml's API look more like the rest of the encoding packages.
http://codereview.appspot.com/5574053
`,
}
func xmlapi(f *ast.File) bool {
if !imports(f, "encoding/xml") {
return false
}
typeof, _ := typecheck(xmlapiTypeConfig, f)
fixed := false
walk(f, func(n interface{}) {
// ... (各種ASTノードのパターンマッチと書き換えロジック)
})
return fixed
}
// xmlMarshal, xmlUnmarshal, xmlCallChain などのヘルパー関数も定義
このファイルは、gofix ツールが encoding/xml のAPI変更を自動的に適用するためのロジックを含んでいます。xmlapi 関数は、Goのソースファイル(AST)を走査し、古い xml.Marshal や xml.NewParser、xml.Parser.Unmarshal の呼び出しパターンを検出し、新しい xml.NewEncoder(...).Encode(...) や xml.NewDecoder(...)、xml.Decoder.DecodeElement(...) のパターンに書き換えます。
関連リンク
- Go言語の
encoding/xmlパッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語の
gofixツールに関する情報: https://go.dev/blog/gofix
参考にした情報源リンク
- Web検索結果: "Go encoding/xml API changes 2012 consistency with other packages"
- Web検索結果: "Go gofix tool purpose"
- Web検索結果: "Go issue 2626" (ただし、このコミットが参照しているIssueは現在のGitHubリポジトリのIssueとは異なる可能性が高い)
golang.org/cl/5574053(このCLの内容は直接取得できませんでした)