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

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

このコミットは、Go言語の標準ライブラリおよびツールにおけるエラーハンドリングの重要な変更を反映しています。具体的には、os.Error 型の使用を廃止し、組み込みの error インターフェースに統一する作業の一環です。これはGo 1.0リリースに向けた言語仕様の安定化と、より柔軟なエラー処理メカニズムの導入を目的としています。

コミット

commit 44526cdbe0c012c2a9bf6fc493aa8ad3411b884f
Author: Russ Cox <rsc@golang.org>
Date:   Tue Nov 1 22:06:05 2011 -0400

    non-pkg: gofix -r error -force=error

    R=golang-dev, iant, r, r
    CC=golang-dev
    https://golang.org/cl/5307066

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

https://github.com/golang/go/commit/44526cdbe0c012c2a9bf6fc493aa8ad3411b884f

元コミット内容

このコミットの元のメッセージは以下の通りです。

non-pkg: gofix -r error -force=error

これは、gofix ツールを使用して、os.Error 型を error インターフェースに置き換える変更を、パッケージ以外のファイル(non-pkg)に対して強制的に(-force=error)適用したことを示しています。

変更の背景

Go言語の初期バージョン(Go 1.0以前)では、エラーを表すために os.Error という具体的な型が使用されていました。しかし、Go言語の設計思想である「インターフェースによる抽象化」の原則に則り、エラー処理をより柔軟かつ拡張可能にする必要性が認識されました。

os.Errorstring 型をラップした構造体であり、エラー情報を文字列としてしか保持できませんでした。これにより、エラーの種類に応じた詳細な情報(例えば、ファイルが見つからないエラーとパーミッションエラーを区別するための追加フィールド)を持たせることが困難でした。

この問題を解決するため、Go開発チームはエラーを表現するための組み込みインターフェース error を導入することを決定しました。この error インターフェースは、Error() string という単一のメソッドを持つ非常にシンプルなものです。これにより、開発者は独自のエラー型を定義し、その型が error インターフェースを満たすように Error() string メソッドを実装することで、よりリッチなエラー情報を扱うことができるようになりました。

このコミットは、os.Error から error への移行作業の一環であり、特にGoの標準ライブラリやドキュメント、ツール内のコードベース全体にわたる大規模な変更を反映しています。gofix ツールが使用されたのは、このような大規模なコードベースの変更を自動化し、開発者の負担を軽減するためです。

前提知識の解説

Go言語におけるエラーハンドリングの基本

Go言語では、例外処理メカニズム(try-catchなど)は採用されていません。その代わりに、関数は通常、最後の戻り値として error 型の値を返します。慣習として、nil はエラーがないことを意味し、非 nilerror 値はエラーが発生したことを示します。

func doSomething() (result string, err error) {
    // ... 処理 ...
    if somethingWentWrong {
        return "", errors.New("something went wrong") // エラーを返す
    }
    return "success", nil // 成功を返す
}

os.Error (旧) と error インターフェース (現行)

  • os.Error (旧): Go言語の初期に存在したエラー型です。これは string 型をラップした構造体であり、エラーメッセージを文字列として保持していました。

    // Goの非常に古いバージョンでの定義例 (概念的なもの)
    package os
    
    type Error struct {
        s string
    }
    
    func (e *Error) String() string {
        return e.s
    }
    
    func NewError(s string) *Error {
        return &Error{s}
    }
    

    この型は、エラーの種類を区別したり、追加のエラーコンテキストを提供したりするのに不便でした。

  • error インターフェース (現行): Go 1.0以降で導入された組み込みのインターフェースです。

    // Go言語の組み込みインターフェース
    type error interface {
        Error() string
    }
    

    このインターフェースは、Error() string という単一のメソッドのみを要求します。これにより、任意の型がこのメソッドを実装するだけで error インターフェースを満たすことができます。

    例えば、カスタムエラー型を定義する場合:

    package main
    
    import "fmt"
    
    type MyCustomError struct {
        Code    int
        Message string
    }
    
    func (e *MyCustomError) Error() string {
        return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
    }
    
    func main() {
        var err error = &MyCustomError{Code: 1001, Message: "Invalid input"}
        fmt.Println(err.Error()) // Output: Error 1001: Invalid input
    }
    

    このように、error インターフェースは、エラー処理の柔軟性と拡張性を大幅に向上させました。

gofix ツール

gofix は、Go言語の古いコードを新しいAPIや言語仕様に自動的に更新するためのコマンドラインツールです。Go言語の進化に伴い、後方互換性を保ちつつ言語や標準ライブラリの改善を行うために、このような自動変換ツールが提供されました。このコミットでは、os.Error から error への移行という大規模な変更を効率的に行うために gofix が活用されました。

技術的詳細

このコミットの技術的詳細は、Go言語のエラーハンドリングモデルの根本的な変更に集約されます。

  1. os.Error 型の削除と error インターフェースへの統一:

    • Goの標準ライブラリから os.Error 型が完全に削除されました。
    • エラーを返す全ての関数、メソッド、インターフェース定義が os.Error から組み込みの error インターフェース型に変更されました。
    • これにより、Goのエラー処理は、特定の具象型に依存するのではなく、Error() string メソッドを持つ任意の型を受け入れる、よりポリモーフィックなアプローチに移行しました。
  2. エラー生成方法の変更:

    • 以前は os.NewError("message") を使用してエラーを生成していましたが、このコミット以降は errors パッケージの errors.New("message") 関数を使用するようになりました。
    • また、より複雑なエラーメッセージやフォーマットが必要な場合は、fmt.Errorf("format string", args...) を使用することが推奨されるようになりました。これは、fmt.Errorf が内部的に error インターフェースを実装した型を返すためです。
  3. エラーメッセージ取得方法の変更:

    • エラーオブジェクトからエラーメッセージを取得する際、以前は err.String() メソッドを使用していましたが、error インターフェースの導入により、err.Error() メソッドを使用するように変更されました。これは、error インターフェースが Error() string メソッドを定義しているためです。
  4. os パッケージからの依存関係の削除:

    • os.Error の廃止に伴い、多くのファイルで os パッケージのインポートが不要になり、削除されました。これにより、コードの依存関係が整理され、よりクリーンな状態になりました。
  5. gofix の活用:

    • この変更は、Goのコードベース全体にわたる非常に広範なものでした。手動での変更は非現実的であるため、gofix ツールがこの移行作業の大部分を自動化するために使用されました。gofix -r error -force=error コマンドは、os.Error の出現箇所を error に置き換え、関連するエラー生成やメッセージ取得の呼び出しも適切に修正する役割を果たしました。

この変更は、Go言語のエラー処理をよりGoらしい(idiomatic)ものにし、開発者がカスタムエラー型を容易に定義できるようにすることで、エラー情報の表現力を高め、より堅牢なアプリケーションを構築するための基盤を強化しました。

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

このコミットでは多数のファイルが変更されていますが、そのほとんどは os.Errorerror に、err.String()err.Error() に、そして os.NewErrorerrors.New に置き換えるというパターンに従っています。

以下に、代表的な変更箇所をいくつか示します。

1. doc/codelab/wiki/final-noclosure.go

--- a/doc/codelab/wiki/final-noclosure.go
+++ b/doc/codelab/wiki/final-noclosure.go
@@ -1,9 +1,9 @@
 package main
 
 import (
+"errors"
 	"http"
 	"io/ioutil"
-"os"
 	"regexp"
 	"template"
 )
@@ -13,12 +13,12 @@ type Page struct {
 	Body  []byte
 }
 
-func (p *Page) save() os.Error {
+func (p *Page) save() error {
 	filename := p.Title + ".txt"
 	return ioutil.WriteFile(filename, p.Body, 0600)
 }
 
-func loadPage(title string) (*Page, os.Error) {
+func loadPage(title string) (*Page, error) {
 	filename := title + ".txt"
 	body, err := ioutil.ReadFile(filename)
 	if err != nil {
@@ -61,21 +61,21 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
 	p := &Page{Title: title, Body: []byte(body)}
 	err = p.save()
 	if err != nil {
-		http.Error(w, err.String(), http.StatusInternalServerError)
+		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
 	http.Redirect(w, r, "/view/"+title, http.StatusFound)
 }
 
 func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
-	t, err := template.ParseFile(tmpl+".html")
+	t, err := template.ParseFile(tmpl + ".html")
 	if err != nil {
-		http.Error(w, err.String(), http.StatusInternalServerError)
+		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
 	err = t.Execute(w, p)
 	if err != nil {
-		http.Error(w, err.String(), http.StatusInternalServerError)
+		http.Error(w, err.Error(), http.StatusInternalServerError)
 	}
 }
 
@@ -83,11 +83,11 @@ const lenPath = len("/view/")
 
 var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
 
-func getTitle(w http.ResponseWriter, r *http.Request) (title string, err os.Error) {
+func getTitle(w http.ResponseWriter, r *http.Request) (title string, err error) {
 	title = r.URL.Path[lenPath:]
 	if !titleValidator.MatchString(title) {
 		http.NotFound(w, r)
-		err = os.NewError("Invalid Page Title")
+		err = errors.New("Invalid Page Title")
 	}
 	return
 }

2. src/cmd/cgo/gcc.go

--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -14,6 +14,7 @@ import (
 	"debug/macho"
 	"debug/pe"
 	"encoding/binary"
+"errors"
 	"flag"
 	"fmt"
 	"go/ast"
@@ -147,10 +148,10 @@ func (p *Package) addToFlag(flag string, args []string) {
 
 // pkgConfig runs pkg-config and extracts --libs and --cflags information
 // for packages.
-func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) {
+func pkgConfig(packages []string) (cflags, ldflags []string, err error) {
 	for _, name := range packages {
 		if len(name) == 0 || name[0] == '-' {
-			return nil, nil, os.NewError(fmt.Sprintf("invalid name: %q", name))
+			return nil, nil, errors.New(fmt.Sprintf("invalid name: %q", name))
 		}
 	}
 
@@ -158,7 +159,7 @@ func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) {
 	stdout, stderr, ok := run(nil, args)
 	if !ok {
 		os.Stderr.Write(stderr)
-		return nil, nil, os.NewError("pkg-config failed")
+		return nil, nil, errors.New("pkg-config failed")
 	}
 	cflags, err = splitQuoted(string(stdout))
 	if err != nil {
@@ -169,7 +170,7 @@ func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) {
 	stdout, stderr, ok = run(nil, args)
 	if !ok {
 		os.Stderr.Write(stderr)
-		return nil, nil, os.NewError("pkg-config failed")
+		return nil, nil, errors.New("pkg-config failed")
 	}
 	ldflags, err = splitQuoted(string(stdout))
 	return
@@ -191,9 +192,9 @@ func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) {
 //
 //     []string{"a", "b:c d", "ef", `g"`}
 //
-func splitQuoted(s string) (r []string, err os.Error) {
+func splitQuoted(s string) (r []string, err error) {
 	var args []string
 	arg := make([]rune, len(s))
 	escaped := false
 	quote := rune(0)
@@ -229,9 +230,9 @@ func splitQuoted(s string) (r []string, err os.Error) {
 		args = append(args, string(arg[:i]))
 	}
 	if quote != 0 {
-		err = os.NewError("unclosed quote")
+		err = errors.New("unclosed quote")
 	} else if escaped {
-		err = os.NewError("unfinished escaping")
+		err = errors.New("unfinished escaping")
 	}
 	return args, err
 }

コアとなるコードの解説

上記の変更箇所は、Go言語のエラーハンドリングのパラダイムシフトを明確に示しています。

  1. import "errors" の追加と import "os" の削除:

    • os.Erroros パッケージの一部であったため、その型を使用していたファイルでは import "os" が必要でした。
    • error インターフェースは組み込み型であるため、通常は明示的なインポートは不要です。しかし、errors.New 関数を使用するためには import "errors" が必要になります。
    • doc/codelab/wiki/final-noclosure.go の例では、os.Error の代わりに error インターフェースと errors.New を使用するように変更されたため、os のインポートが削除され、errors のインポートが追加されています。
  2. 関数の戻り値の型変更:

    • func (p *Page) save() os.Errorfunc (p *Page) save() error に変更されています。これは、関数が返すエラーの型が、具体的な os.Error 型から、より抽象的な error インターフェース型に変わったことを意味します。これにより、save メソッドは、error インターフェースを満たす任意のカスタムエラー型を返すことができるようになり、柔軟性が向上します。
  3. エラーメッセージの取得方法の変更:

    • http.Error(w, err.String(), http.StatusInternalServerError)http.Error(w, err.Error(), http.StatusInternalServerError) に変更されています。
    • これは、os.Error 型が String() メソッドを持っていたのに対し、error インターフェースは Error() メソッドを定義しているためです。この変更により、エラーオブジェクトからエラーメッセージを取得する標準的な方法が統一されました。
  4. エラーの生成方法の変更:

    • err = os.NewError("Invalid Page Title")err = errors.New("Invalid Page Title") に変更されています。
    • これは、エラーを生成するためのファクトリ関数が os パッケージから errors パッケージに移行したことを示しています。errors.New は、与えられた文字列をエラーメッセージとする新しい error インスタンスを返します。
  5. pkgConfig 関数におけるエラー生成:

    • src/cmd/cgo/gcc.gopkgConfig 関数では、os.NewError を使ってエラーを生成していた箇所が errors.New に変更されています。また、fmt.Sprintf を使ってエラーメッセージを生成している箇所も同様に errors.New に変更されています。これは、エラー生成の新しい慣習に従ったものです。

これらの変更は、Go言語のエラー処理が、よりインターフェース指向で、柔軟かつ統一されたものになったことを示しています。これにより、開発者はエラーをより効果的に分類し、処理し、デバッグできるようになりました。

関連リンク

参考にした情報源リンク