[インデックス 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の複雑性を不必要に高めているだけの存在でした。
このような未使用の戻り値を削除することで、以下のようなメリットがあります。
- APIの簡素化: 関数シグネチャがシンプルになり、利用者が理解しやすくなります。
- コードの可読性向上: 不要な変数の宣言や代入がなくなるため、コードがよりクリーンになります。
- 保守性の向上: 将来的にこの未使用の戻り値が誤って利用されたり、混乱を招いたりする可能性がなくなります。
- パフォーマンスの微細な改善: 非常に微細ではありますが、不要な戻り値の処理がなくなることで、わずかながら実行時のオーバーヘッドが削減される可能性があります。
この変更は、Go言語のツールチェイン全体でコードの品質と効率性を高めるための継続的な取り組みの一環として行われました。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とツールに関する知識が必要です。
-
AST (Abstract Syntax Tree - 抽象構文木): プログラムのソースコードを、その構文構造を反映したツリー形式で表現したものです。Go言語では、
go/ast
パッケージがASTの表現と操作を提供します。コンパイラやコード分析ツール、整形ツール(gofmt
など)は、ソースコードをASTに変換し、それを操作することで様々な処理を行います。 -
go/printer
パッケージ: Go言語のASTを整形("pretty-print")して、人間が読める形式のGoソースコードとして出力するためのパッケージです。このパッケージは、gofmt
やgofix
といったGoの標準ツールの中核をなす部分です。ASTを整形する際には、インデント、改行、空白の挿入など、Goの公式なスタイルガイドに沿ったフォーマットが適用されます。 -
io.Writer
インターフェース: Go言語の標準ライブラリio
パッケージで定義されているインターフェースです。データを書き込むための抽象化を提供します。最も重要なメソッドはWrite([]byte) (n int, err error)
で、バイトスライスを書き込み、書き込まれたバイト数n
とエラーerr
を返します。os.Stdout
、bytes.Buffer
、ファイルなどがこのインターフェースを実装しています。 -
Go言語の多値戻り値: Go言語の関数は複数の値を返すことができます。これはエラーハンドリングで特に頻繁に利用され、慣習的に最後の戻り値は
error
型となります。例えば、func foo() (resultType, error)
のように定義されます。io.Writer
のWrite
メソッドのように、操作の成功を示す値(書き込まれたバイト数など)とエラーを同時に返すパターンはGoでは一般的です。 -
gofmt
ツール: Go言語の公式なコード整形ツールです。Goのソースコードを標準的なスタイルに自動的に整形します。go/printer
パッケージを内部的に利用しています。開発者が手動でフォーマットを気にする必要がなくなり、コードベース全体で一貫したスタイルを保つことができます。 -
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
という戻り値は、gofix
やgofmt
、テストコードなど、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に影響を与えるため、このパッケージを利用しているgofix
、gofmt
、そしてgo/printer
自身のテストコードも、新しい関数シグネチャに合わせて修正されています。具体的には、Fprint
の呼び出し箇所で、最初の戻り値を受け取る部分が削除されています。
この種の変更は、Go言語の標準ライブラリが成熟していく過程で、APIの洗練と最適化が行われる典型的な例です。未使用のコードや冗長なAPI要素を特定し、削除することで、ライブラリ全体の品質と使いやすさが向上します。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
-
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 ```
-
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() + ">" }
-
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 }
-
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 }
-
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))
}
このコードでは、Fprint
はint
とerror
の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がより正確にその振る舞いを表現し、呼び出し元での不要な_
による値の破棄が不要になります。
また、パッケージレベルのヘルパー関数であるFprint
(Config
なしで呼び出せるもの)も同様に修正されています。
変更前:
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
の変更
これらのファイルは、それぞれgofix
とgofmt
ツールのエントリポイントまたは主要な処理ロジックを含んでいます。両ツールとも、Goのソースコードを整形するためにgo/printer
パッケージのFprint
関数を利用しています。
変更前は、Fprint
の呼び出しは以下の形式でした。
_, err := printConfig.Fprint(&buf, fset, f)
ここで、_
はFprint
が返す最初の戻り値(書き込まれたバイト数)を破棄するために使用されていました。
変更後:
err := printConfig.Fprint(&buf, fset, f)
Fprint
がerror
のみを返すようになったため、_
が不要になり、直接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)
}
同様に、Fprint
がerror
のみを返すようになったため、_
が削除され、直接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公式ドキュメント)