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

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

このコミットは、Go言語の標準ライブラリ内の多数のパッケージ(特にnetold/netchanold/regexpold/templateos/userpatchpath/filepathpathrandreflectregexprpcruntime/pprofscannersmtpstrconvstringssyscallsyslogtabwritertemplatetestingtimeunicodeurlwebsocketxml)におけるエラーハンドリングの変更を適用するものです。具体的には、os.Error型を組み込みのerrorインターフェースに置き換え、エラーオブジェクトの文字列化メソッドをString()からError()に変更しています。これは、gofixツールを用いて自動的に適用された大規模なリファクタリングです。

コミット

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

    src/pkg/[n-z]*: gofix -r error -force=error

    R=golang-dev, bsiegert, iant
    CC=golang-dev
    https://golang.org/cl/5294074

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

https://github.com/golang/go/commit/eb6929299b6da3d9bea1fa7f7cd319c2de9242bb

元コミット内容

このコミットは、gofix -r error -force=errorコマンドを実行した結果を反映しています。これは、Go言語の進化に伴い、エラーハンドリングのAPIが変更されたことに対する自動的なコード修正です。具体的には、以前のos.Error型がGoの組み込み型であるerrorインターフェースに統合されたこと、およびエラーメッセージを取得するためのメソッドがString()からError()に変更されたことへの対応です。この変更は、src/pkg/以下のnからzで始まるパッケージに適用されています。

変更の背景

Go言語は初期の段階から活発に開発が進められており、APIの設計も継続的に改善されていました。このコミットが行われた2011年頃は、Go言語がバージョン1.0のリリースに向けて安定化を進めていた時期にあたります。

初期のGo言語では、エラーを表すためにos.Errorという具体的な型が使用されていました。しかし、Goの設計思想として、インターフェースによる抽象化と柔軟性が重視されるようになり、エラーハンドリングもその例外ではありませんでした。特定の具象型に依存するのではなく、Error() stringメソッドを持つ任意の型をエラーとして扱えるように、組み込みのerrorインターフェースが導入されました。

この変更により、開発者はカスタムエラー型を容易に定義できるようになり、より表現力豊かで型安全なエラーハンドリングが可能になりました。しかし、既存のコードベースにはos.Errorを使用している箇所が多数存在したため、これらのコードを新しいerrorインターフェースに移行するための自動化ツールであるgofixが提供されました。このコミットは、そのgofixツールがGo標準ライブラリの該当箇所に適用された結果です。

前提知識の解説

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

Go言語では、例外処理のメカニズム(try-catchなど)は採用されていません。その代わりに、関数がエラーを返す場合は、戻り値の最後の要素としてerror型の値を返すという慣習があります。

  • errorインターフェース: Goの組み込みインターフェースで、以下のように定義されています。

    type error interface {
        Error() string
    }
    

    このインターフェースを満たす任意の型はエラーとして扱うことができます。Error()メソッドは、エラーの文字列表現を返します。

  • os.Error (旧): このコミット以前に存在した型で、エラーを表すために使われていました。これは、Goの初期の設計におけるエラー表現方法でした。このコミットにより、os.Errorerrorインターフェースに置き換えられました。

gofixツール

gofixは、Go言語のソースコードを自動的に書き換えるためのツールです。Go言語のAPIが変更された際に、古いAPIを使用しているコードを新しいAPIに準拠させるために使用されます。これにより、大規模なコードベースの移行作業が大幅に簡素化されます。gofixは、Goの抽象構文木(AST)を解析し、定義されたルールに基づいてコードを変換します。

このコミットで使用されているgofix -r error -force=errorは、errorに関連する特定の修正ルールを適用することを意味します。-force=errorは、このerror修正ルールに対して、通常は手動での確認が必要な場合でも強制的に適用するようなオプションであったと考えられます。

技術的詳細

このコミットの主要な技術的変更点は以下の2点です。

  1. os.Error型からerrorインターフェースへの移行: Goの関数やメソッドのシグネチャにおいて、エラーを返す型がos.Errorから組み込みのerrorインターフェースに変更されています。 例:

    • 変更前: func someFunc() (resultType, err os.Error)
    • 変更後: func someFunc() (resultType, err error)

    この変更は、Goのエラーハンドリングの柔軟性を高めるための重要なステップでした。これにより、標準ライブラリのエラー型だけでなく、ユーザーが独自に定義したエラー型も統一的に扱えるようになります。

  2. エラーメッセージ取得メソッドのString()からError()への変更: エラーオブジェクトの文字列表現を取得する際に、以前はerr.String()が使用されていましたが、errorインターフェースの導入に伴い、err.Error()メソッドを使用するように変更されました。 例:

    • 変更前: fmt.Println("Error:", err.String())
    • 変更後: fmt.Println("Error:", err.Error())

    これは、errorインターフェースがError() stringメソッドを要求するため、そのインターフェースに準拠するための変更です。

これらの変更は、Go言語のエラーハンドリングの標準化と、より堅牢なエラー処理メカニズムの確立に貢献しました。

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

このコミットは非常に多くのファイルにわたる変更を含んでいますが、そのほとんどは上記のos.Errorからerrorへの型変更、およびerr.String()からerr.Error()へのメソッド呼び出しの変更です。

例として、src/pkg/net/cgo_stub.gosrc/pkg/net/dnsclient.goの変更を見てみましょう。

src/pkg/net/cgo_stub.goの変更例:

--- a/src/pkg/net/cgo_stub.go
+++ b/src/pkg/net/cgo_stub.go
@@ -8,20 +8,18 @@
 
 package net
 
-import "os"
-
-func cgoLookupHost(name string) (addrs []string, err os.Error, completed bool) {
+func cgoLookupHost(name string) (addrs []string, err error, completed bool) {
 	return nil, nil, false
 }
 
-func cgoLookupPort(network, service string) (port int, err os.Error, completed bool) {
+func cgoLookupPort(network, service string) (port int, err error, completed bool) {
 	return 0, nil, false
 }
 
-func cgoLookupIP(name string) (addrs []IP, err os.Error, completed bool) {
+func cgoLookupIP(name string) (addrs []IP, err error, completed bool) {
 	return nil, nil, false
 }
 
-func cgoLookupCNAME(name string) (cname string, err os.Error, completed bool) {
+func cgoLookupCNAME(name string) (cname string, err error, completed bool) {
 	return "", nil, false
 }

ここでは、関数の戻り値の型がos.Errorからerrorに一括で変更されています。また、import "os"が不要になったため削除されています。

src/pkg/net/dnsclient.goの変更例:

--- a/src/pkg/net/dnsclient.go
+++ b/src/pkg/net/dnsclient.go
@@ -7,20 +7,19 @@ package net
 import (
 	"bytes"
 	"fmt"
-	"os"
 	"rand"
 	"sort"
 )
 
 // DNSError represents a DNS lookup error.
 type DNSError struct {
-	Error     string // description of the error
+	Err       string // description of the error
 	Name      string // name looked for
 	Server    string // server used
 	IsTimeout bool
 }
 
-func (e *DNSError) String() string {
+func (e *DNSError) Error() string {
 	if e == nil {
 		return "<nil>"
 	}
@@ -28,7 +27,7 @@ func (e *DNSError) String() string {
 	if e.Server != "" {
 		s += " on " + e.Server
 	}
-	s += ": " + e.Error
+	s += ": " + e.Err
 	return s
 }
 
@@ -40,10 +39,10 @@ const noSuchHost = "no such host"
 // reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
 // address addr suitable for rDNS (PTR) record lookup or an error if it fails
 // to parse the IP address.
-func reverseaddr(addr string) (arpa string, err os.Error) {
+func reverseaddr(addr string) (arpa string, err error) {
 	ip := ParseIP(addr)
 	if ip == nil {
-		return "", &DNSError{Error: "unrecognized address", Name: addr}
+		return "", &DNSError{Err: "unrecognized address", Name: addr}
 	}
 	if ip.To4() != nil {
 		return fmt.Sprintf("%d.%d.%d.%d.in-addr.arpa.", ip[15], ip[14], ip[13], ip[12]), nil
@@ -64,18 +63,18 @@ func reverseaddr(addr string) (arpa string, err os.Error) {
 
 // Find answer for name in dns message.
 // On return, if err == nil, addrs != nil.
-func answer(name, server string, dns *dnsMsg, qtype uint16) (cname string, addrs []dnsRR, err os.Error) {
+func answer(name, server string, dns *dnsMsg, qtype uint16) (cname string, addrs []dnsRR, err error) {
 	addrs = make([]dnsRR, 0, len(dns.answer))
 
 	if dns.rcode == dnsRcodeNameError && dns.recursion_available {
-		return "", nil, &DNSError{Error: noSuchHost, Name: name}
+		return "", nil, &DNSError{Err: noSuchHost, Name: name}
 	}
 	if dns.rcode != dnsRcodeSuccess {
 		// None of the error codes make sense
 		// for the query we sent.  If we didn't get
 		// a name error and we didn't get success,
 		// the server is behaving incorrectly.
-		return "", nil, &DNSError{Error: "server misbehaving", Name: name, Server: server}
+		return "", nil, &DNSError{Err: "server misbehaving", Name: name, Server: server}
 	}
 
 	// Look for the name.
@@ -107,12 +106,12 @@ Cname:
 			}
 		}
 		if len(addrs) == 0 {
-			return "", nil, &DNSError{Error: noSuchHost, Name: name, Server: server}
+			return "", nil, &DNSError{Err: noSuchHost, Name: name, Server: server}
 		}
 		return name, addrs, nil
 	}
 
-	return "", nil, &DNSError{Error: "too many redirects", Name: name, Server: server}
+	return "", nil, &DNSError{Err: "too many redirects", Name: name, Server: server}
 }
 
 func isDomainName(s string) bool {

ここでは、DNSError構造体のフィールド名がErrorからErrに変更され、String()メソッドがError()メソッドにリネームされています。また、DNSErrorの初期化時にErrorフィールドではなくErrフィールドを使用するように変更されています。

これらの変更は、Go言語のエラーハンドリングの統一性と一貫性を高めるためのものであり、gofixツールによって自動的に適用されたため、手動での大規模な修正作業を回避できました。

コアとなるコードの解説

このコミットにおける「コアとなるコードの変更」は、特定の機能追加やバグ修正ではなく、Go言語のエラーハンドリングの基盤となるAPIの変更とその適用です。

具体的には、Goの組み込み型であるerrorインターフェースが、エラーを表現するための標準的な方法として確立されたことが最も重要です。これにより、Goのコードベース全体でエラーの扱いが一貫するようになりました。

  • errorインターフェースの採用: Goの設計哲学の一つに「インターフェースによる抽象化」があります。errorインターフェースは、この哲学をエラーハンドリングに適用したものです。これにより、関数は具体的なエラー型を返すのではなく、errorインターフェースを返すことで、呼び出し元はエラーの具体的な実装に依存することなく、エラーが発生したかどうか、そしてその文字列表現を取得できるようになります。

  • Error() stringメソッドの標準化: errorインターフェースがError() stringメソッドを要求することで、すべてのアラー型はエラーメッセージを文字列として提供する統一された方法を持つことになります。これは、エラーのログ出力やユーザーへの表示において非常に便利です。

この変更は、Go言語の安定性と将来の拡張性にとって不可欠なものでした。gofixのようなツールが存在することで、このような大規模なAPI変更も比較的スムーズに行うことができ、開発者は新しいAPIの恩恵を受けつつ、既存のコードベースを容易に更新することが可能になりました。

関連リンク

  • Go言語の公式ドキュメント: https://go.dev/
  • Go言語のエラーハンドリングに関する公式ブログ記事 (関連する可能性のある情報): https://go.dev/blog/error-handling-and-go (このコミットより後の記事ですが、Goのエラーハンドリングの哲学を理解するのに役立ちます)
  • gofixツールの情報 (Goのバージョンによって異なる場合があります): https://pkg.go.dev/cmd/gofix

参考にした情報源リンク