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

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

このコミットは、Go言語の標準ライブラリである encoding/xml パッケージのAPIを、他の encoding パッケージ(encoding/jsonencoding/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 パッケージは、他のパッケージと比較してストリーム処理の概念が希薄であり、MarshalUnmarshal 関数が io.Writerio.Reader を直接引数に取るなど、使い勝手の面で改善の余地がありました。

このコミットは、Go 1のリリースに向けて、標準ライブラリ全体のAPIの一貫性を高めるという大きな目標の一環として行われました。特に、encoding/xmlencoding/jsonencoding/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.Readerio.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)のパターンに合わせることにあります。

  1. Parser から Decoder への名称変更と機能拡張:

    • 既存の xml.Parser 型が xml.Decoder に名称変更されました。これは、他の encoding パッケージにおける json.Decodergob.Decoder との整合性を図るためです。
    • Decoder 型には、新たに Decode メソッドが追加されました。これにより、NewDecoder(r io.Reader).Decode(v interface{}) という形式で、ストリームから直接Goの構造体にデコードできるようになりました。
    • Unmarshal メソッドは DecodeElement に名称変更され、特定の開始要素からデコードする機能を提供します。
  2. 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{}) というパターンに移行しました。
  3. 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{}) というパターンに移行しました。
  4. 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 では完全にカバーされていません。これは、Unmarshalio.Reader ではなく []byte を受け取るようになったため、io.Reader を渡している既存のコードを自動的に NewDecoder を使用するように変換することが難しいことを意味します。
  5. 内部実装の変更:

    • src/pkg/encoding/xml/xml.go 内の Parser 構造体とその関連メソッドが Decoder にリネームされ、d.Strict, d.AutoClose, d.Entity などのフィールドアクセスも p. から d. に変更されました。
    • src/pkg/encoding/xml/marshal.goEncoder 型と NewEncoder 関数、Encode メソッドが追加されました。
    • src/pkg/encoding/xml/read.goUnmarshal 関数のシグネチャ変更と、DecoderDecode および 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 を使用して NewEncoderEncode メソッドを呼び出すことで、ストリームへの書き込みロジックを再利用しています。Encoder 型が導入され、NewEncoderio.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 型には、DecodeDecodeElement の2つの新しいメソッドが追加され、それぞれストリーム全体または特定の要素からのデコードを可能にしています。また、Parserunmarshal メソッドのレシーバが 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.Marshalxml.NewParserxml.Parser.Unmarshal の呼び出しパターンを検出し、新しい xml.NewEncoder(...).Encode(...)xml.NewDecoder(...)xml.Decoder.DecodeElement(...) のパターンに書き換えます。

関連リンク

参考にした情報源リンク

  • 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の内容は直接取得できませんでした)