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

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

このコミットは、Go言語のコマンドラインツール go のサブコマンドである go fix におけるエラーメッセージの改善を目的としています。具体的には、go fix コマンドにパッケージディレクトリではない引数が与えられた際に表示されるエラーメッセージが、より分かりやすく、ユーザーにとって有益な情報を提供するように修正されました。以前は「何も役に立たない」メッセージが表示されていたものが、この変更により「パッケージが見つかりません」という趣旨のメッセージが表示されるようになっています。

コミット

  • コミットハッシュ: 108961b21649cd7c2d8f9650542b2228fea09613
  • 作者: Rob Pike r@golang.org
  • コミット日時: 2012年1月29日 (日) 11:06:39 -0800
  • 変更ファイル: src/cmd/go/pkg.go (1ファイル)
  • 変更行数: 4行 (2行追加, 2行削除)

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

https://github.com/golang/go/commit/108961b21649cd7c2d8f9650542b2228fea09613

元コミット内容

    cmd/go: slightly less confusing error message
    If the argument to go fix isn't a package directory, the message said nothing helpful.
    Now it at least says a package couldn't be found.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5577072

変更の背景

Go言語の go コマンドは、Goプログラムの開発を支援するための多機能なツールです。その中の go fix サブコマンドは、古いGoプログラムを新しいGoのバージョンで動作するように自動的に修正する役割を担っています。

このコミットが行われる以前は、go fix コマンドに、Goのパッケージとして認識されないディレクトリやファイルが引数として渡された場合、ユーザーにとって意味不明なエラーメッセージが表示されていました。具体的には、単にエラーオブジェクトの内容がそのまま出力されるだけで、何が問題なのか、どのように対処すればよいのかが全く伝わらない状態でした。

開発者は、このような不親切なエラーメッセージがユーザーの混乱を招き、開発体験を損なうことを認識していました。特に、Go言語に不慣れなユーザーや、コマンドの正しい使い方を理解していないユーザーにとっては、エラーの原因を特定することが困難であり、フラストレーションの原因となっていました。

この背景から、エラーメッセージをより具体的で分かりやすいものに改善し、ユーザーが問題の原因を即座に理解し、適切な対応を取れるようにすることが喫緊の課題とされました。このコミットは、その課題を解決するための一歩として、エラーメッセージに「パッケージが見つかりません」という明確な文言を追加することで、ユーザーエクスペリエンスの向上を図ったものです。

前提知識の解説

Go言語の go コマンド

go コマンドは、Go言語のソースコードのコンパイル、テスト、依存関係の管理、ドキュメント生成など、Go開発における様々なタスクを実行するための主要なツールです。go build, go run, go test, go get など、多くのサブコマンドを持っています。

go fix コマンド

go fixgo コマンドのサブコマンドの一つで、Go言語のバージョンアップに伴うAPIの変更や非推奨になった機能などに対応するため、古いGoのソースコードを自動的に修正するツールです。例えば、Go 1.0からGo 1.1への移行時に、一部の標準ライブラリの関数名が変更された場合などに、go fix を実行することで、手動でコードを修正する手間を省くことができます。これは、Go言語が後方互換性を重視しつつも、言語や標準ライブラリの進化を続けるための重要なメカニズムです。

Go言語のパッケージ

Go言語では、コードは「パッケージ」という単位で管理されます。パッケージは、関連する機能や型、関数などをまとめたもので、再利用可能なコードの最小単位となります。Goのソースファイルは必ず package 宣言で始まる必要があり、通常はディレクトリごとに一つのパッケージが対応します。go コマンドは、このパッケージの概念に基づいて動作します。例えば、go buildgo test は、指定されたパッケージを対象に処理を行います。

Go言語のエラーハンドリング

Go言語では、エラーは error インターフェースを実装した値として扱われます。関数は、通常、戻り値の最後の要素として error 型の値を返します。エラーが発生しなかった場合は nil を返し、エラーが発生した場合は nil ではない error 値を返します。呼び出し側は、この error 値をチェックすることで、処理が成功したか失敗したかを判断します。

このコミットで変更されている errorf 関数は、Goの標準ライブラリや内部ツールでよく使われる、フォーマットされたエラーメッセージを生成するための関数です。C言語の printf のように、フォーマット文字列と引数を受け取り、それらを組み合わせてエラーメッセージ文字列を生成します。

技術的詳細

このコミットの技術的な変更は、src/cmd/go/pkg.go ファイル内の errorf 関数の呼び出し方に関するものです。

pkg.go ファイルは、go コマンドがGoのパッケージをロードし、処理するためのロジックを含んでいます。packages 関数と packagesForBuild 関数は、コマンドライン引数として与えられたパスからGoのパッケージを特定し、ロードする役割を担っています。

変更前は、これらの関数内でパッケージのロードに失敗した場合、以下のように errorf が呼び出されていました。

errorf("%s", pkg.Error)

ここで pkg.Error は、パッケージのロード中に発生した具体的なエラーオブジェクトです。%s フォーマット指定子は、error インターフェースを実装するオブジェクトが String() メソッド(または Error() メソッド)を持つ場合、そのメソッドの戻り値を使って文字列に変換されます。

この挙動により、pkg.Error が例えば「指定されたパスにパッケージが見つからない」といった内容のエラーオブジェクトであった場合、そのエラーオブジェクトが持つ文字列表現がそのままユーザーに表示されていました。しかし、この文字列表現が必ずしもユーザーにとって分かりやすいとは限りませんでした。特に、内部的なエラーメッセージや、文脈が不足しているメッセージの場合、ユーザーは何が問題なのかを理解できませんでした。

このコミットでは、errorf の呼び出しを以下のように変更しました。

errorf("can't load package: %s", pkg.Error)

この変更により、エラーメッセージの先頭に "can't load package: " という固定のプレフィックスが追加されるようになりました。これにより、どのような pkg.Error が返されたとしても、ユーザーは「パッケージのロードに失敗した」という明確な文脈を最初に受け取ることができます。その後に続く pkg.Error の具体的な内容が、なぜロードに失敗したのかの詳細を補足する形になります。

この修正は、Go言語のエラーメッセージ設計における一般的なプラクティスを反映しています。それは、エラーメッセージはユーザーが問題を理解し、解決するために十分な情報と文脈を提供すべきであるという考え方です。単に内部的なエラーコードやメッセージをそのまま表示するのではなく、ユーザーが直面している状況(この場合は「パッケージのロード」)と、その結果(「失敗」)を明確に伝えることで、ユーザーエクスペリエンスが大幅に向上します。

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

変更は src/cmd/go/pkg.go ファイルの2箇所で行われています。

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -407,7 +407,7 @@ func packages(args []string) []*Package {
 	for _, arg := range args {
 		pkg := loadPackage(arg, &stk)
 		if pkg.Error != nil {
-			errorf("%s", pkg.Error)
+			errorf("can't load package: %s", pkg.Error)
 			continue
 		}
 		pkgs = append(pkgs, pkg)
@@ -437,7 +437,7 @@ func packagesForBuild(args []string) []*Package {
 	printed := map[*PackageError]bool{}
 	for _, pkg := range pkgs {
 		if pkg.Error != nil {
-			errorf("%s", pkg.Error)
+			errorf("can't load package: %s", pkg.Error)
 		}
 		for _, err := range pkg.DepsErrors {
 			// Since these are errors in dependencies,

コアとなるコードの解説

上記のコードスニペットは、src/cmd/go/pkg.go ファイル内の packages 関数と packagesForBuild 関数における変更を示しています。

  1. packages 関数内の変更: この関数は、コマンドライン引数として与えられた文字列(通常はパッケージパス)をGoのパッケージとしてロードしようとします。 pkg := loadPackage(arg, &stk) の行で実際にパッケージのロードが行われ、その結果が pkg 変数に格納されます。 if pkg.Error != nil の条件は、パッケージのロード中にエラーが発生したかどうかをチェックしています。 変更前は errorf("%s", pkg.Error) となっており、pkg.Error オブジェクトの文字列表現が直接エラーメッセージとして出力されていました。 変更後は errorf("can't load package: %s", pkg.Error) となり、"can't load package: " という固定の文字列がエラーメッセージのプレフィックスとして追加されました。これにより、ユーザーはエラーが「パッケージのロード」に関連していることを明確に理解できるようになりました。

  2. packagesForBuild 関数内の変更: この関数も同様にパッケージを処理しますが、主にビルドに関連するコンテキストで使用されます。 for _, pkg := range pkgs ループ内で、各パッケージの pkg.Error をチェックしています。 ここでも、packages 関数と同様に、errorf("%s", pkg.Error)errorf("can't load package: %s", pkg.Error) に変更されました。 この変更により、ビルドプロセス中にパッケージのロードエラーが発生した場合も、より分かりやすいメッセージがユーザーに提供されるようになりました。

両方の変更箇所で、エラーメッセージのフォーマット文字列に "can't load package: " という固定の文字列が追加されたことが、このコミットの核心です。これにより、go fix コマンド(およびパッケージロードエラーが発生する可能性のある他の go コマンド)が、ユーザーにとってより有益で理解しやすいエラーメッセージを出力するようになりました。これは、Goツールのユーザーフレンドリーさを向上させるための小さな、しかし重要な改善です。

関連リンク

参考にした情報源リンク