[インデックス 10188] ファイルの概要
このコミットは、Go言語のexec
パッケージにおいて、コマンドの実行失敗時に返されるエラーの型を明確にするために、ExitError
という新しいエラー型を導入するものです。これにより、*os.Waitmsg
がエラーとして直接使用されていた既存の慣習を改善し、よりGoらしいエラーハンドリングのパターンに準拠させることが目的です。
コミット
commit 451a1fa46d0449dc6982b38ba51cf94ebc750eca
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 1 21:49:44 2011 -0400
exec: introduce ExitError
The existing code uses *os.Waitmsg as an os.Error,
but *os.Waitmsg is really just a stringer.
Introduce an explicit error type for the real error.
Not to be submitted until just before error goes in;
the gofix for error updates type assertions
err.(*os.Waitmsg)
to
err.(*exec.ExitError)
The seemingly redundant String method will become
an Error method when error goes in, and will no longer
be redundant.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5331044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/451a1fa46d0449dc6982b38ba51cf94ebc750eca
元コミット内容
このコミットの目的は、exec
パッケージにExitError
を導入することです。既存のコードでは*os.Waitmsg
がos.Error
として使用されていましたが、*os.Waitmsg
は実際には単なる文字列化(Stringer)の機能しか持っていませんでした。そのため、真のエラーを表すための明示的なエラー型を導入します。
この変更は、Go言語のエラー処理に関する大きな変更(error
インターフェースの導入)が適用される直前までコミットされません。gofix
ツールは、この変更に伴い、既存のerr.(*os.Waitmsg)
という型アサーションをerr.(*exec.ExitError)
に自動的に更新します。
また、ExitError
に定義されている一見冗長に見えるString
メソッドは、将来的にerror
インターフェースが導入された際にError
メソッドとなり、冗長ではなくなります。
変更の背景
Go言語の初期のバージョンでは、エラーハンドリングのメカニズムが現在とは異なっていました。特に、エラーを表すための統一されたインターフェースerror
がまだ完全に確立されておらず、様々な型がエラーとして扱われることがありました。
このコミットが行われた2011年11月時点では、Go言語はまだ活発に開発されており、言語仕様や標準ライブラリの設計が進化している段階でした。exec
パッケージでは、外部コマンドの実行結果を表すために*os.Waitmsg
がエラーとして返されていましたが、これはos.Waitmsg
が本来、プロセスの終了ステータスやシグナルに関する情報を持つ構造体であり、エラーとしての振る舞いを直接意図したものではなかったため、設計上の不整合がありました。
このコミットの背景には、Go言語全体でエラーハンドリングをより一貫性のある、慣用的なものにするという大きな流れがありました。具体的には、error
インターフェースの導入と、それに伴うエラー型の明確化が推進されていました。*os.Waitmsg
のような、本来エラーではない型がエラーとして扱われる状況は、コードの可読性や保守性を損なう可能性がありました。
ExitError
の導入は、コマンドの実行失敗という特定のエラーケースに対して、よりセマンティックで明確な型を提供することを目的としています。これにより、開発者はエラーの種類を型アサーションによって容易に判別できるようになり、より堅牢なエラーハンドリングロジックを記述できるようになります。また、コミットメッセージにある「The seemingly redundant String method will become an Error method when error goes in」という記述は、Go言語におけるerror
インターフェースの設計思想、すなわちError() string
メソッドを持つ任意の型がerror
インターフェースを満たすという原則を反映しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とツールの知識が必要です。
-
os.Waitmsg
:os
パッケージの一部で、子プロセスの終了ステータスに関する情報(終了コード、シグナルなど)をカプセル化する構造体です。exec
パッケージで外部コマンドを実行し、そのプロセスが終了した際に、このWaitmsg
が生成されます。初期のGoでは、このWaitmsg
が直接エラーとして返されることがありました。しかし、Waitmsg
自体はエラーメッセージを返すString()
メソッドを持つものの、Goのエラーインターフェースが確立される前の段階では、エラーとしての振る舞いが曖昧でした。 -
os.Error
(初期のGoにおけるエラーインターフェース): Go言語の初期のバージョンでは、現在のような組み込みのerror
インターフェースが存在せず、os.Error
という型がエラーを表すために使われていました。これは、String() string
メソッドを持つインターフェースとして定義されており、任意の型がこのメソッドを実装することでエラーとして扱われることができました。このコミットの時点では、まだerror
という組み込みインターフェースへの移行期にあり、os.Error
が使われている箇所が残っていました。 -
型アサーション (
err.(*Type)
): Go言語における型アサーションは、インターフェース型の変数が、特定の具象型(または別のインターフェース型)の値を保持しているかどうかをチェックし、その値にアクセスするために使用されます。例えば、err.(*os.Waitmsg)
は、err
がos.Waitmsg
型の値を保持している場合に、その値を取り出すことを試みます。このコミットでは、エラーの型が*os.Waitmsg
から*exec.ExitError
に変更されるため、既存の型アサーションも更新する必要がありました。 -
gofix
ツール:gofix
は、Go言語のバージョンアップやAPIの変更に伴い、古いコードを新しいAPIや慣用的な書き方に自動的に変換するためのコマンドラインツールです。Go言語は後方互換性を重視していますが、言語や標準ライブラリの進化の過程で、一部のAPIや慣習が変更されることがあります。gofix
は、このような変更に対して開発者が手動でコードを修正する手間を省くために提供されました。このコミットでは、os.Waitmsg
からExitError
への移行に伴う型アサーションの変更をgofix
が自動的に処理することが言及されており、当時のGo開発におけるgofix
の重要性を示しています。 -
String()
メソッドとError()
メソッド: Go言語では、fmt
パッケージのStringer
インターフェース(String() string
メソッドを持つ)を実装することで、その型の値を文字列として表現できるようになります。一方、Goの組み込みerror
インターフェースは、Error() string
メソッドを持つことを要求します。このコミットの時点では、ExitError
はString()
メソッドを実装していますが、将来的にerror
インターフェースが導入された際に、このString()
メソッドがError()
メソッドとして機能するようになることが示唆されています。これは、Goのエラーインターフェース設計が、Stringer
インターフェースの概念を拡張してエラーメッセージの表現に利用していることを示しています。
技術的詳細
このコミットの核心は、exec
パッケージにおけるエラーハンドリングのセマンティクスを改善することにあります。
ExitError
構造体の導入
// An ExitError reports an unsuccessful exit by a command.
type ExitError struct {
*os.Waitmsg
}
func (e *ExitError) String() string {
return e.Waitmsg.String()
}
type ExitError struct { *os.Waitmsg }
:ExitError
は、*os.Waitmsg
を匿名フィールドとして埋め込む構造体として定義されています。これにより、ExitError
はos.Waitmsg
のすべてのメソッドとフィールドを「継承」したかのように振る舞うことができます。特に、os.Waitmsg
が持つプロセスの終了ステータスに関する情報(例えば、ExitStatus()
やExited()
など)に、ExitError
のインスタンスを通じて直接アクセスできるようになります。これはGoのコンポジション(埋め込み)の強力な例であり、既存のos.Waitmsg
の機能を再利用しつつ、新しいセマンティックな型を導入しています。func (e *ExitError) String() string { return e.Waitmsg.String() }
:ExitError
はString()
メソッドを実装しています。このメソッドは、埋め込まれた*os.Waitmsg
のString()
メソッドを呼び出すことで、エラーメッセージを生成します。コミットメッセージにあるように、このString()
メソッドは将来的にGoの組み込みerror
インターフェースのError()
メソッドとして機能するようになります。これにより、ExitError
はerror
インターフェースを満たし、Goのエラーハンドリングの慣用的なパターンに適合します。
Cmd.Run()
および Cmd.Wait()
の変更
exec.Cmd
のRun()
メソッドとWait()
メソッドは、外部コマンドの実行と終了を待機する主要な関数です。これらのメソッドは、コマンドが正常に終了しなかった場合にエラーを返します。
変更前は、これらのメソッドは直接*os.Waitmsg
をエラーとして返していました。
// 変更前 (抜粋)
// If the command fails to run or doesn't complete successfully, the
// error is of type *os.Waitmsg. Other error types may be
// returned for I/O problems.
func (c *Cmd) Run() os.Error {
// ...
} else if !msg.Exited() || msg.ExitStatus() != 0 {
return msg // ここで直接 *os.Waitmsg を返していた
}
// ...
}
変更後は、*os.Waitmsg
を直接返す代わりに、&ExitError{msg}
という形でExitError
のインスタンスを返します。
// 変更後 (抜粋)
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (c *Cmd) Run() os.Error {
// ...
} else if !msg.Exited() || msg.ExitStatus() != 0 {
return &ExitError{msg} // ExitError のインスタンスを返す
}
// ...
}
この変更により、コマンドの実行失敗によるエラーは、明確に*exec.ExitError
型として識別できるようになります。これにより、エラーハンドリングのコードで型アサーションを使用する際に、より具体的なエラータイプをチェックできるようになります。
テストコードの変更
テストコードsrc/pkg/exec/exec_test.go
も、この変更に合わせて更新されています。特に、エラーの型アサーションが*os.Waitmsg
から*exec.ExitError
に変更されています。
// 変更前 (抜粋)
// if _, ok := err.(*os.Waitmsg); !ok {
// t.Errorf("expected Waitmsg from cat combined; got %T: %v", err, err)
// }
// 変更後 (抜粋)
if _, ok := err.(*ExitError); !ok {
t.Errorf("expected *ExitError from cat combined; got %T: %v", err, err)
}
これは、この変更が単なる内部的なリファクタリングではなく、exec
パッケージの公開API(エラーの返却型)に影響を与えるものであることを示しています。gofix
ツールがこの種の変更を自動的に処理できることは、Go言語の進化における重要な側面です。
コアとなるコードの変更箇所
src/pkg/exec/exec.go
--- a/src/pkg/exec/exec.go
+++ b/src/pkg/exec/exec.go
@@ -203,7 +203,7 @@ func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err os.Error) {
// status.
//
// If the command fails to run or doesn't complete successfully, the
-// error is of type *os.Waitmsg. Other error types may be
+// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (c *Cmd) Run() os.Error {
if err := c.Start(); err != nil {
@@ -256,6 +256,15 @@ func (c *Cmd) Start() os.Error {
return nil
}
+// An ExitError reports an unsuccessful exit by a command.
+type ExitError struct {
+ *os.Waitmsg
+}
+
+func (e *ExitError) String() string {
+ return e.Waitmsg.String()
+}
+
// Wait waits for the command to exit.
// It must have been started by Start.
//
@@ -264,7 +273,7 @@ func (c *Cmd) Start() os.Error {
// status.
//
// If the command fails to run or doesn't complete successfully, the
-// error is of type *os.Waitmsg. Other error types may be
+// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (c *Cmd) Wait() os.Error {
if c.Process == nil {
@@ -290,7 +299,7 @@ func (c *Cmd) Wait() os.Error {
if err != nil {
return err
} else if !msg.Exited() || msg.ExitStatus() != 0 {
- return msg
+ return &ExitError{msg}
}
return copyError
src/pkg/exec/exec_test.go
--- a/src/pkg/exec/exec_test.go
+++ b/src/pkg/exec/exec_test.go
@@ -53,8 +53,8 @@ func TestCatStdin(t *testing.T) {
func TestCatGoodAndBadFile(t *testing.T) {
// Testing combined output and error values.
bs, err := helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
- if _, ok := err.(*os.Waitmsg); !ok {
- t.Errorf("expected Waitmsg from cat combined; got %T: %v", err, err)
+ if _, ok := err.(*ExitError); !ok {
+ t.Errorf("expected *ExitError from cat combined; got %T: %v", err, err)
}
s := string(bs)
sp := strings.SplitN(s, "\n", 2)
@@ -81,12 +81,12 @@ func TestNoExistBinary(t *testing.T) {
func TestExitStatus(t *testing.T) {
// Test that exit values are returned correctly
err := helperCommand("exit", "42").Run()
- if werr, ok := err.(*os.Waitmsg); ok {
+ if werr, ok := err.(*ExitError); ok {
if s, e := werr.String(), "exit status 42"; s != e {
t.Errorf("from exit 42 got exit %q, want %q", s, e)
}
} else {
- t.Fatalf("expected Waitmsg from exit 42; got %T: %v", err, err)
+ t.Fatalf("expected *ExitError from exit 42; got %T: %v", err, err)
}
}
コアとなるコードの解説
src/pkg/exec/exec.go
の変更点
-
コメントの更新:
Cmd.Run()
およびCmd.Wait()
メソッドのドキュメンテーションコメントが更新され、コマンドの実行失敗時に返されるエラーの型が*os.Waitmsg
から*ExitError
に変更されたことが明記されています。これは、APIの変更をユーザーに明確に伝えるための重要な変更です。 -
ExitError
型の定義:Cmd.Start()
メソッドの後に、新しい型ExitError
が定義されています。type ExitError struct { *os.Waitmsg }
この構造体は、
*os.Waitmsg
を匿名フィールドとして埋め込んでいます。これにより、ExitError
のインスタンスはos.Waitmsg
のすべてのフィールドとメソッド(例:ExitStatus()
,Exited()
,String()
など)に直接アクセスできるようになります。これはGoのコンポジションの典型的な使用例であり、既存の機能を再利用しつつ、よりセマンティックな新しい型を作成しています。 -
ExitError
のString()
メソッド:ExitError
型にString()
メソッドが追加されています。func (e *ExitError) String() string { return e.Waitmsg.String() }
このメソッドは、埋め込まれた
*os.Waitmsg
のString()
メソッドを呼び出すことで、エラーの文字列表現を返します。コミットメッセージにあるように、このString()
メソッドは将来的にGoの組み込みerror
インターフェースのError()
メソッドとして機能するようになります。これにより、ExitError
はerror
インターフェースを満たし、Goのエラーハンドリングの慣用的なパターンに適合します。 -
Cmd.Wait()
におけるエラー返却の変更:Cmd.Wait()
メソッド内で、コマンドが正常に終了しなかった場合のエラー返却ロジックが変更されています。// 変更前: return msg // 変更後: return &ExitError{msg}
以前は
*os.Waitmsg
型の変数msg
を直接返していましたが、変更後はmsg
をラップした&ExitError{msg}
という*ExitError
型の値を返すようになります。これにより、呼び出し元はコマンドの終了エラーを*exec.ExitError
として型アサーションできるようになり、より具体的なエラーハンドリングが可能になります。
src/pkg/exec/exec_test.go
の変更点
- 型アサーションの更新:
TestCatGoodAndBadFile
とTestExitStatus
というテスト関数内で、エラーの型アサーションが変更されています。// 変更前: if _, ok := err.(*os.Waitmsg); !ok { ... } // 変更後: if _, ok := err.(*ExitError); !ok { ... }
これらの変更は、// 変更前: if werr, ok := err.(*os.Waitmsg); ok { ... } else { ... } // 変更後: if werr, ok := err.(*ExitError); ok { ... } else { ... }
exec
パッケージが返すエラーの型が*os.Waitmsg
から*exec.ExitError
に変わったことを反映しています。テストコードが新しいエラー型を正しく認識し、それに基づいてエラーの検証を行うように修正されています。これは、APIの変更が正しく実装され、既存の動作が維持されていることを確認するために不可欠な変更です。
これらの変更により、Goのexec
パッケージは、外部コマンドの実行失敗という特定のエラーケースに対して、より明確でセマンティックなエラー型を提供するようになり、Go言語全体のエラーハンドリングの一貫性向上に貢献しています。
関連リンク
- Gerrit Change-Id:
https://golang.org/cl/5331044
(GoプロジェクトのコードレビューシステムであるGerritへのリンク。このコミットのレビュープロセスや関連する議論を追うことができます。)
参考にした情報源リンク
- Go言語のエラーハンドリングに関する一般的な情報:
os.Waitmsg
に関する情報:- Go言語の
gofix
ツールに関する情報: - Go言語の歴史と進化に関する情報(特にエラーハンドリングの変遷について言及されている可能性のある記事やドキュメント):
- The Go Programming Language Blog (特に初期のブログ記事)
- Go 1 and the Future of Go Programs (Go 1リリースに関する記事で、後方互換性や
gofix
の役割について触れられている可能性があります。)
- Go言語におけるコンポジション(埋め込み)に関する情報:
これらのリンクは、Go言語の初期のエラーハンドリング、os.Waitmsg
の役割、gofix
ツールの目的、そしてGoのエラーインターフェースの進化について、より深い理解を得るのに役立ちます。I have generated the comprehensive technical explanation for commit 10188 as requested, following all your instructions regarding the content, structure, and language.