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

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

このコミットは、Go言語の実験的なEBNFパーサー (exp/ebnf) における手動での修正を記録しています。特に、エラー処理に関するAPIの変更に対応するためのものです。

コミット

  • コミットハッシュ: c93b6a1756be708ba2d6c8c91c4dabdbbd653cbe
  • Author: Russ Cox rsc@golang.org
  • Date: Tue Nov 1 21:49:33 2011 -0400
  • 変更ファイル: src/pkg/exp/ebnf/parser.go

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

https://github.com/golang/go/commit/c93b6a1756be708ba2d6c8c91c4dabdbbd653cbe

元コミット内容

    exp/ebnf: manual fixup for error
    
    (The definition of ErrorList is in another file, so gofix
    has no hope of getting this right.)
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/5330043

変更の背景

このコミットの背景には、Go言語の進化におけるエラーハンドリングAPIの変更と、gofixツールの限界があります。

Go言語の初期バージョンでは、エラーを表すためにos.Errorという型が使われていました。しかし、Go 1.0のリリースに向けて、より汎用的なerrorインターフェースが導入され、エラーハンドリングの仕組みが大きく変更されました。これに伴い、os.Errorを使用していた既存のコードはerrorインターフェースに準拠するように修正する必要がありました。

gofixは、このようなGo言語のAPI変更や慣習の進化に合わせて、古いコードを新しい形式に自動的に変換するためのツールです。しかし、このコミットメッセージにあるように、ErrorListの定義が別のファイルにある場合、gofixは依存関係を完全に解決できず、自動的に正しい修正を適用することができませんでした。

したがって、このコミットは、gofixが自動修正できなかった部分を、開発者であるRuss Cox氏が手動で修正したことを示しています。これは、Go言語の進化の過程で、自動化ツールだけでは対応しきれない複雑なケースが存在したことを物語っています。

前提知識の解説

EBNF (Extended Backus-Naur Form)

EBNF(拡張バッカス・ナウア記法)は、プログラミング言語やデータ形式の構文を記述するためのメタ言語です。BNF(バッカス・ナウア記法)を拡張したもので、より簡潔で読みやすい構文定義を可能にします。EBNFは、以下のような特徴を持ちます。

  • 繰り返し: *(0回以上)、+(1回以上)
  • 選択: |(いずれか)
  • オプション: [](0回または1回)
  • グループ化: ()
  • 終端記号と非終端記号: 終端記号はそれ以上分解できない要素(例: キーワード、識別子)、非終端記号は他の規則によって定義される要素(例: 式、文)

Go言語のexp/ebnfパッケージは、このEBNF形式で記述された構文定義を解析するための実験的なパーサーを提供していたと考えられます。

Go言語のエラーハンドリング (os.Errorからerrorインターフェースへ)

Go言語のエラーハンドリングは、他の多くの言語に見られるような例外処理(try-catch)とは異なり、関数の戻り値としてエラーを返すという特徴的なアプローチを取ります。

  • os.Error (Go言語初期): Go言語の非常に初期の段階では、エラーを表すためにos.Errorという具体的な型が使われていました。これは、エラーメッセージを保持するシンプルなインターフェースのようなものでした。

  • errorインターフェース (Go 1.0以降): Go 1.0のリリースに伴い、エラーハンドリングの標準的な方法としてerrorインターフェースが導入されました。このインターフェースは非常にシンプルで、Error() stringという単一のメソッドを定義しています。

    type error interface {
        Error() string
    }
    

    これにより、任意の型がError()メソッドを実装することでerrorインターフェースを満たすことができ、より柔軟なエラー表現が可能になりました。例えば、fmt.Errorf関数は、このerrorインターフェースを返すことで、カスタムエラーメッセージを生成します。

このコミットが行われた2011年11月は、Go 1.0のリリース(2012年3月)に先立つ時期であり、Go言語のAPIが活発に開発・変更されていた過渡期にあたります。os.Errorからerrorインターフェースへの移行は、Go言語の安定化と標準化に向けた重要な変更の一つでした。

gofixツール

gofixは、Go言語のソースコードを自動的に修正し、新しいAPIや慣習に適合させるためのコマンドラインツールです。Go言語の進化の過程で、APIの変更や言語仕様の微調整が行われることがありましたが、gofixは開発者が手動で大量のコードを修正する手間を省くために開発されました。

例えば、Go言語の初期にはmake関数がスライス、マップ、チャネルの作成に使われていましたが、後にスライスにはnew関数が推奨されるようになりました。gofixはこのような変更を検出し、自動的にコードを修正することができます。

しかし、このコミットのケースのように、型定義が別のファイルにあり、gofixがその依存関係を完全に追跡できない場合など、一部の複雑なシナリオでは手動での修正が必要となることがありました。

技術的詳細

変更の核心は、p.errors.Error()からp.errors.Err()への呼び出しの変更です。

Go言語のerrorインターフェースにはError()というメソッドが定義されていますが、これは通常、エラーオブジェクト自体がエラーメッセージを文字列として返すために使用されます。

一方、Err()というメソッドは、Go言語の標準ライブラリや特定のパッケージで、エラーのリストや複数のエラーを管理する構造体(この場合はErrorListと推測される)が、それらのエラーをまとめて単一のerrorインターフェースとして返すために使用されることがあります。

この変更は、p.errorsErrorListのような型であり、その型がError()メソッドではなくErr()メソッドを通じて、集約されたエラー情報をerrorインターフェースとして提供するようにAPIが変更されたことを示唆しています。

具体的には、ErrorListのようなエラーの集合を扱う型が、Go 1.0のエラーハンドリングの慣習に合わせて、以下のような変更を受けた可能性があります。

  • 旧API: ErrorList型が直接Error() stringメソッドを実装しており、エラーメッセージの文字列を返していた。
  • 新API: ErrorList型がErr() errorメソッドを実装し、内部のエラーリストから単一のerrorインターフェース(例えば、最初のエラーや、複数のエラーを結合したカスタムエラー型)を返すようになった。これにより、ErrorList自体がerrorインターフェースを直接実装するのではなく、Err()を通じてerrorインターフェースを「提供」する形になったと考えられます。

この変更は、Go言語のエラーハンドリングがより洗練され、エラーの集約や伝播のパターンが標準化されていく過程の一部と見ることができます。

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

--- a/src/pkg/exp/ebnf/parser.go
+++ b/src/pkg/exp/ebnf/parser.go
@@ -187,5 +187,5 @@ func (p *parser) parse(filename string, src io.Reader) Grammar {
 func Parse(filename string, src io.Reader) (Grammar, os.Error) {
 	var p parser
 	grammar := p.parse(filename, src)
-	return grammar, p.errors.Error()
+	return grammar, p.errors.Err()
 }

コアとなるコードの解説

変更が行われたのは、src/pkg/exp/ebnf/parser.goファイル内のParse関数です。

func Parse(filename string, src io.Reader) (Grammar, os.Error) {
	var p parser
	grammar := p.parse(filename, src)
	return grammar, p.errors.Err() // 変更箇所
}
  • func Parse(filename string, src io.Reader) (Grammar, os.Error): この関数は、指定されたファイル名とio.ReaderからEBNF構文を解析し、Grammar(解析された構文ツリーまたは表現)とos.Error(エラー情報)を返します。Go 1.0以前のos.Errorが戻り値の型として使われている点に注目してください。
  • var p parser: parser型の変数を宣言しています。これはEBNF解析のロジックをカプセル化する構造体であると推測されます。
  • grammar := p.parse(filename, src): parserparseメソッドを呼び出して、実際の解析処理を実行し、結果としてGrammarオブジェクトを取得しています。
  • return grammar, p.errors.Err(): ここが変更点です。
    • 変更前: p.errors.Error()
    • 変更後: p.errors.Err()

p.errorsは、解析中に発生したエラーを収集するErrorListのような型であると推測されます。このErrorListが、Go言語のエラーハンドリングAPIの変更に伴い、エラー情報を取得するためのメソッドがError()からErr()に変わったため、それに合わせて呼び出し側も修正されたということです。

この修正により、Parse関数は、ErrorListが提供する新しいAPIを通じて、適切にエラー情報をos.Error(最終的にはerrorインターフェース)として返すことができるようになりました。

関連リンク

参考にした情報源リンク