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

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

このコミットは、Go言語の標準ライブラリであるgo/printerパッケージにおける、未使用の戻り値の削除に関する変更です。具体的には、AST (Abstract Syntax Tree) を整形して出力するFprint関数から、書き込まれたバイト数を示すint型の戻り値が削除されました。この戻り値はコード内で一切使用されていなかったため、APIの簡素化とコードのクリーンアップを目的としています。

コミット

  • コミットハッシュ: 4874d14180282fd426f0bbf279a8ac2e9ea3a5aa
  • 作者: Robert Griesemer gri@golang.org
  • コミット日時: 2011年11月22日 火曜日 15:27:10 -0800
  • コミットメッセージ:
    go/printer: remove "written" result value - is never used
    
    R=r
    CC=golang-dev
    https://golang.org/cl/5436052
    

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

https://github.com/golang/go/commit/4874d14180282fd426f0bbf279a8ac2e9ea3a5aa

元コミット内容

go/printer: remove "written" result value - is never used

このコミットは、go/printerパッケージのFprint関数から、書き込まれたバイト数を示す「written」という結果値(戻り値)を削除するものです。この戻り値はコードのどこでも使用されていなかったため、不要と判断されました。

変更の背景

Go言語の設計哲学の一つに「シンプルさ」と「不要なものの排除」があります。このコミットの背景には、まさにその哲学が反映されています。

go/printerパッケージのFprint関数は、GoのAST(抽象構文木)を整形してio.Writerに書き出す機能を提供します。通常、io.WriterインターフェースのWriteメソッドや、それに類する出力関数は、書き込んだバイト数とエラーを (n int, err error)の形式で返します。これは、部分的な書き込みが発生した場合や、書き込みが成功したバイト数を確認する必要がある場合に有用です。

しかし、このコミットが行われた時点でのgo/printerパッケージのFprint関数は、常に書き込みバイト数として0を返しており、かつその戻り値が呼び出し元で一切利用されていませんでした。つまり、このint型の戻り値は「デッドコード」であり、APIの複雑性を不必要に高めているだけの存在でした。

このような未使用の戻り値を削除することで、以下のようなメリットがあります。

  1. APIの簡素化: 関数シグネチャがシンプルになり、利用者が理解しやすくなります。
  2. コードの可読性向上: 不要な変数の宣言や代入がなくなるため、コードがよりクリーンになります。
  3. 保守性の向上: 将来的にこの未使用の戻り値が誤って利用されたり、混乱を招いたりする可能性がなくなります。
  4. パフォーマンスの微細な改善: 非常に微細ではありますが、不要な戻り値の処理がなくなることで、わずかながら実行時のオーバーヘッドが削減される可能性があります。

この変更は、Go言語のツールチェイン全体でコードの品質と効率性を高めるための継続的な取り組みの一環として行われました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とツールに関する知識が必要です。

  1. AST (Abstract Syntax Tree - 抽象構文木): プログラムのソースコードを、その構文構造を反映したツリー形式で表現したものです。Go言語では、go/astパッケージがASTの表現と操作を提供します。コンパイラやコード分析ツール、整形ツール(gofmtなど)は、ソースコードをASTに変換し、それを操作することで様々な処理を行います。

  2. go/printerパッケージ: Go言語のASTを整形("pretty-print")して、人間が読める形式のGoソースコードとして出力するためのパッケージです。このパッケージは、gofmtgofixといったGoの標準ツールの中核をなす部分です。ASTを整形する際には、インデント、改行、空白の挿入など、Goの公式なスタイルガイドに沿ったフォーマットが適用されます。

  3. io.Writerインターフェース: Go言語の標準ライブラリioパッケージで定義されているインターフェースです。データを書き込むための抽象化を提供します。最も重要なメソッドはWrite([]byte) (n int, err error)で、バイトスライスを書き込み、書き込まれたバイト数nとエラーerrを返します。os.Stdoutbytes.Buffer、ファイルなどがこのインターフェースを実装しています。

  4. Go言語の多値戻り値: Go言語の関数は複数の値を返すことができます。これはエラーハンドリングで特に頻繁に利用され、慣習的に最後の戻り値はerror型となります。例えば、func foo() (resultType, error)のように定義されます。io.WriterWriteメソッドのように、操作の成功を示す値(書き込まれたバイト数など)とエラーを同時に返すパターンはGoでは一般的です。

  5. gofmtツール: Go言語の公式なコード整形ツールです。Goのソースコードを標準的なスタイルに自動的に整形します。go/printerパッケージを内部的に利用しています。開発者が手動でフォーマットを気にする必要がなくなり、コードベース全体で一貫したスタイルを保つことができます。

  6. gofixツール: Go言語の古いコードを新しいAPIや言語仕様に合わせて自動的に修正するツールです。Go言語の進化に伴い、APIの変更や非推奨化が行われることがありますが、gofixはこれらの変更に追従して既存のコードを更新するのに役立ちます。このツールもgo/printerパッケージを利用して、修正後のASTを整形して出力します。

技術的詳細

このコミットの核心は、go/printerパッケージ内のFprint関数のシグネチャ変更です。

変更前、Fprint関数は以下のようなシグネチャを持っていました。

func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, error)

ここで、最初の戻り値intは「書き込まれたバイト数」を意図していました。しかし、実際のfprint(内部で呼び出される非公開関数)の実装では、この値は常に0を返していました。

// Note: The number of bytes written is always 0 and should be ignored.
func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, error) {
	return 0, cfg.fprint(output, fset, node, make(map[ast.Node]int))
}

そして、この0という戻り値は、gofixgofmt、テストコードなど、Fprintを呼び出す全ての箇所で無視されていました。例えば、以下のようなコードが変更前は存在していました。

_, err := printConfig.Fprint(&buf, fset, f) // 最初の戻り値は `_` で破棄

この状況は、APIの誤解を招く可能性があり、また不要な情報を提供していました。そこで、このコミットでは、Fprint関数のシグネチャからint型の戻り値を削除し、errorのみを返すように変更しました。

変更後のFprint関数のシグネチャは以下のようになります。

func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error

これにより、Fprintの呼び出し元では、不要な_による戻り値の破棄が不要になり、コードがより簡潔になりました。

この変更は、go/printerパッケージの公開APIに影響を与えるため、このパッケージを利用しているgofixgofmt、そしてgo/printer自身のテストコードも、新しい関数シグネチャに合わせて修正されています。具体的には、Fprintの呼び出し箇所で、最初の戻り値を受け取る部分が削除されています。

この種の変更は、Go言語の標準ライブラリが成熟していく過程で、APIの洗練と最適化が行われる典型的な例です。未使用のコードや冗長なAPI要素を特定し、削除することで、ライブラリ全体の品質と使いやすさが向上します。

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

このコミットで変更された主要なファイルとコードスニペットは以下の通りです。

  1. src/pkg/go/printer/printer.go: Fprint関数のシグネチャと実装の変更

    --- a/src/pkg/go/printer/printer.go
    +++ b/src/pkg/go/printer/printer.go
    @@ -1000,21 +1000,18 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{\n     return\n    }\n    \n    -// Fprint "pretty-prints" an AST node to output and returns the number\n    -// of bytes written and an error (if any) for a given configuration cfg.\n    +// Fprint "pretty-prints" an AST node to output for a given configuration cfg.\n    // Position information is interpreted relative to the file set fset.\n    // The node type must be *ast.File, or assignment-compatible to ast.Expr,\n    // ast.Decl, ast.Spec, or ast.Stmt.\n    -// Note: The number of bytes written is always 0 and should be ignored.\n    //\n    -func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, error) {\n    -	return 0, cfg.fprint(output, fset, node, make(map[ast.Node]int))\n    +func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {\n    +	return cfg.fprint(output, fset, node, make(map[ast.Node]int))\n    }\n    \n    // Fprint "pretty-prints" an AST node to output.\n    // It calls Config.Fprint with default settings.\n    //\n    func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {\n    -	_, err := (&Config{Tabwidth: 8}).Fprint(output, fset, node)\n    -	return err\n    +	return (&Config{Tabwidth: 8}).Fprint(output, fset, node)\n    }\n    ```
    
    
  2. src/cmd/gofix/main.go: Fprint呼び出し箇所の修正

    --- a/src/cmd/gofix/main.go
    +++ b/src/cmd/gofix/main.go
    @@ -109,7 +109,7 @@ func gofmtFile(f *ast.File) ([]byte, error) {
     	var buf bytes.Buffer
     
     	ast.SortImports(fset, f)
    -	_, err := printConfig.Fprint(&buf, fset, f)
    +	err := printConfig.Fprint(&buf, fset, f)
     	if err != nil {
     		return nil, err
     	}
    @@ -203,7 +203,7 @@ var gofmtBuf bytes.Buffer
     
     func gofmt(n interface{}) string {
     	gofmtBuf.Reset()
    -	_, err := printConfig.Fprint(&gofmtBuf, fset, n)
    +	err := printConfig.Fprint(&gofmtBuf, fset, n)
     	if err != nil {
     		return "<" + err.Error() + ">"
     	}
    
  3. src/cmd/gofmt/gofmt.go: Fprint呼び出し箇所の修正

    --- a/src/cmd/gofmt/gofmt.go
    +++ b/src/cmd/gofmt/gofmt.go
    @@ -121,7 +121,7 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error
     	}
     
     	var buf bytes.Buffer
    -	_, err = (&printer.Config{printerMode, *tabWidth}).Fprint(&buf, fset, file)
    +	err = (&printer.Config{printerMode, *tabWidth}).Fprint(&buf, fset, file)
     	if err != nil {
     		return err
     	}
    
  4. src/pkg/go/printer/performance_test.go: テストコードの修正

    --- a/src/pkg/go/printer/performance_test.go
    +++ b/src/pkg/go/printer/performance_test.go
    @@ -20,7 +20,7 @@ import (\n var testfile *ast.File\n \n func testprint(out io.Writer, file *ast.File) {\n-\tif _, err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil {\n+\tif err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil {\n     	log.Fatalf("print error: %s", err)\n     }\n    }
    
  5. src/pkg/go/printer/printer_test.go: テストコードの修正

    --- a/src/pkg/go/printer/printer_test.go
    +++ b/src/pkg/go/printer/printer_test.go
    @@ -62,7 +62,7 @@ func runcheck(t *testing.T, source, golden string, mode checkMode) {\n     \n     // format source\n     var buf bytes.Buffer\n-\tif _, err := cfg.Fprint(&buf, fset, prog); err != nil {\n+\tif err := cfg.Fprint(&buf, fset, prog); err != nil {\n     		t.Error(err)\n     	}\n     	res := buf.Bytes()\n    ```
    
    

コアとなるコードの解説

src/pkg/go/printer/printer.go の変更

このファイルはgo/printerパッケージの主要な実装を含んでいます。 最も重要な変更は、Config型のメソッドであるFprintのシグネチャです。

変更前:

func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, error) {
	return 0, cfg.fprint(output, fset, node, make(map[ast.Node]int))
}

このコードでは、Fprintinterrorの2つの値を返していました。しかし、intの値は常に0であり、内部の非公開関数cfg.fprintが返すエラー値のみが意味を持っていました。コメントにも「Note: The number of bytes written is always 0 and should be ignored.」と明記されており、この戻り値が不要であることが示唆されていました。

変更後:

func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
	return cfg.fprint(output, fset, node, make(map[ast.Node]int))
}

変更後は、int型の戻り値が削除され、errorのみを返すようになりました。これにより、APIがより正確にその振る舞いを表現し、呼び出し元での不要な_による値の破棄が不要になります。

また、パッケージレベルのヘルパー関数であるFprintConfigなしで呼び出せるもの)も同様に修正されています。

変更前:

func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
	_, err := (&Config{Tabwidth: 8}).Fprint(output, fset, node)
	return err
}

変更後:

func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
	return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
}

こちらも、内部で呼び出すConfig.Fprintの戻り値からintが削除されたため、_による破棄が不要になり、直接errorを返す形に簡素化されています。

src/cmd/gofix/main.go および src/cmd/gofmt/gofmt.go の変更

これらのファイルは、それぞれgofixgofmtツールのエントリポイントまたは主要な処理ロジックを含んでいます。両ツールとも、Goのソースコードを整形するためにgo/printerパッケージのFprint関数を利用しています。

変更前は、Fprintの呼び出しは以下の形式でした。

_, err := printConfig.Fprint(&buf, fset, f)

ここで、_Fprintが返す最初の戻り値(書き込まれたバイト数)を破棄するために使用されていました。

変更後:

err := printConfig.Fprint(&buf, fset, f)

Fprinterrorのみを返すようになったため、_が不要になり、直接err変数にエラーを受け取れるようになりました。これにより、コードがより簡潔で読みやすくなっています。

src/pkg/go/printer/performance_test.go および src/pkg/go/printer/printer_test.go の変更

これらのファイルは、go/printerパッケージのテストコードです。テストコードもまた、Fprint関数を呼び出してその動作を検証しています。

変更前は、テストコード内でもFprintの戻り値のint部分を_で破棄していました。

if _, err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil {
    log.Fatalf("print error: %s", err)
}

変更後:

if err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil {
    log.Fatalf("print error: %s", err)
}

同様に、Fprinterrorのみを返すようになったため、_が削除され、直接err変数にエラーを受け取る形に修正されています。これにより、テストコードも新しいAPIシグネチャに適合し、一貫性が保たれています。

これらの変更は全体として、Go言語のコードベースにおける「シンプルさ」と「不要なものの排除」という原則を徹底するものであり、APIの明確化とコードの保守性向上に貢献しています。

関連リンク

  • Gerrit Change-Id: https://golang.org/cl/5436052 (GoプロジェクトのコードレビューシステムであるGerritへのリンク)

参考にした情報源リンク

  • Go言語公式ドキュメント: go/printerパッケージ
  • Go言語公式ドキュメント: io.Writerインターフェース
  • Go言語公式ドキュメント: go/astパッケージ
  • Go言語公式ドキュメント: gofmtコマンド
  • Go言語公式ドキュメント: gofixコマンド
  • Go言語の多値戻り値に関する一般的な情報源 (例: Go by Example - Multiple Return Values)
  • Gitのコミットと差分に関する一般的な情報源 (例: Git公式ドキュメント)