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

[インデックス 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.Waitmsgos.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言語の概念とツールの知識が必要です。

  1. os.Waitmsg: osパッケージの一部で、子プロセスの終了ステータスに関する情報(終了コード、シグナルなど)をカプセル化する構造体です。execパッケージで外部コマンドを実行し、そのプロセスが終了した際に、このWaitmsgが生成されます。初期のGoでは、このWaitmsgが直接エラーとして返されることがありました。しかし、Waitmsg自体はエラーメッセージを返すString()メソッドを持つものの、Goのエラーインターフェースが確立される前の段階では、エラーとしての振る舞いが曖昧でした。

  2. os.Error (初期のGoにおけるエラーインターフェース): Go言語の初期のバージョンでは、現在のような組み込みのerrorインターフェースが存在せず、os.Errorという型がエラーを表すために使われていました。これは、String() stringメソッドを持つインターフェースとして定義されており、任意の型がこのメソッドを実装することでエラーとして扱われることができました。このコミットの時点では、まだerrorという組み込みインターフェースへの移行期にあり、os.Errorが使われている箇所が残っていました。

  3. 型アサーション (err.(*Type)): Go言語における型アサーションは、インターフェース型の変数が、特定の具象型(または別のインターフェース型)の値を保持しているかどうかをチェックし、その値にアクセスするために使用されます。例えば、err.(*os.Waitmsg)は、erros.Waitmsg型の値を保持している場合に、その値を取り出すことを試みます。このコミットでは、エラーの型が*os.Waitmsgから*exec.ExitErrorに変更されるため、既存の型アサーションも更新する必要がありました。

  4. gofixツール: gofixは、Go言語のバージョンアップやAPIの変更に伴い、古いコードを新しいAPIや慣用的な書き方に自動的に変換するためのコマンドラインツールです。Go言語は後方互換性を重視していますが、言語や標準ライブラリの進化の過程で、一部のAPIや慣習が変更されることがあります。gofixは、このような変更に対して開発者が手動でコードを修正する手間を省くために提供されました。このコミットでは、os.WaitmsgからExitErrorへの移行に伴う型アサーションの変更をgofixが自動的に処理することが言及されており、当時のGo開発におけるgofixの重要性を示しています。

  5. String()メソッドとError()メソッド: Go言語では、fmtパッケージのStringerインターフェース(String() stringメソッドを持つ)を実装することで、その型の値を文字列として表現できるようになります。一方、Goの組み込みerrorインターフェースは、Error() stringメソッドを持つことを要求します。このコミットの時点では、ExitErrorString()メソッドを実装していますが、将来的に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を匿名フィールドとして埋め込む構造体として定義されています。これにより、ExitErroros.Waitmsgのすべてのメソッドとフィールドを「継承」したかのように振る舞うことができます。特に、os.Waitmsgが持つプロセスの終了ステータスに関する情報(例えば、ExitStatus()Exited()など)に、ExitErrorのインスタンスを通じて直接アクセスできるようになります。これはGoのコンポジション(埋め込み)の強力な例であり、既存のos.Waitmsgの機能を再利用しつつ、新しいセマンティックな型を導入しています。
  • func (e *ExitError) String() string { return e.Waitmsg.String() }: ExitErrorString()メソッドを実装しています。このメソッドは、埋め込まれた*os.WaitmsgString()メソッドを呼び出すことで、エラーメッセージを生成します。コミットメッセージにあるように、このString()メソッドは将来的にGoの組み込みerrorインターフェースのError()メソッドとして機能するようになります。これにより、ExitErrorerrorインターフェースを満たし、Goのエラーハンドリングの慣用的なパターンに適合します。

Cmd.Run() および Cmd.Wait() の変更

exec.CmdRun()メソッドと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 の変更点

  1. コメントの更新: Cmd.Run()およびCmd.Wait()メソッドのドキュメンテーションコメントが更新され、コマンドの実行失敗時に返されるエラーの型が*os.Waitmsgから*ExitErrorに変更されたことが明記されています。これは、APIの変更をユーザーに明確に伝えるための重要な変更です。

  2. ExitError型の定義: Cmd.Start()メソッドの後に、新しい型ExitErrorが定義されています。

    type ExitError struct {
    	*os.Waitmsg
    }
    

    この構造体は、*os.Waitmsgを匿名フィールドとして埋め込んでいます。これにより、ExitErrorのインスタンスはos.Waitmsgのすべてのフィールドとメソッド(例: ExitStatus(), Exited(), String()など)に直接アクセスできるようになります。これはGoのコンポジションの典型的な使用例であり、既存の機能を再利用しつつ、よりセマンティックな新しい型を作成しています。

  3. ExitErrorString()メソッド: ExitError型にString()メソッドが追加されています。

    func (e *ExitError) String() string {
    	return e.Waitmsg.String()
    }
    

    このメソッドは、埋め込まれた*os.WaitmsgString()メソッドを呼び出すことで、エラーの文字列表現を返します。コミットメッセージにあるように、このString()メソッドは将来的にGoの組み込みerrorインターフェースのError()メソッドとして機能するようになります。これにより、ExitErrorerrorインターフェースを満たし、Goのエラーハンドリングの慣用的なパターンに適合します。

  4. Cmd.Wait()におけるエラー返却の変更: Cmd.Wait()メソッド内で、コマンドが正常に終了しなかった場合のエラー返却ロジックが変更されています。

    // 変更前: return msg
    // 変更後: return &ExitError{msg}
    

    以前は*os.Waitmsg型の変数msgを直接返していましたが、変更後はmsgをラップした&ExitError{msg}という*ExitError型の値を返すようになります。これにより、呼び出し元はコマンドの終了エラーを*exec.ExitErrorとして型アサーションできるようになり、より具体的なエラーハンドリングが可能になります。

src/pkg/exec/exec_test.go の変更点

  1. 型アサーションの更新: TestCatGoodAndBadFileTestExitStatusというテスト関数内で、エラーの型アサーションが変更されています。
    // 変更前: 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の役割、gofixツールの目的、そしてGoのエラーインターフェースの進化について、より深い理解を得るのに役立ちます。I have generated the comprehensive technical explanation for commit 10188 as requested, following all your instructions regarding the content, structure, and language.