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

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

このコミットは、Go言語の標準ライブラリ net パッケージから、InvalidConnError および UnknownSocketError という2つのエラー型を削除するものです。これらの型は未使用であり、ドキュメント化されておらず、特に InvalidConnError はGoのエラーハンドリングのイディオムに沿っていないと判断されました。

コミット

commit d3285f2a796f4fc856da9a15ca8a7dbff418aea1
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Jan 31 15:04:42 2012 -0800

    net: remove types InvalidConnError and UnknownSocketError

    Both are unused and undocumented.

    InvalidConnError is also non-idiomatic: a FooError type can
    typically describe something, else it would be an ErrFoo
    variable.

    R=golang-dev, alex.brainman
    CC=golang-dev
    https://golang.org/cl/5609045

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

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

元コミット内容

net: remove types InvalidConnError and UnknownSocketError

Both are unused and undocumented.

InvalidConnError is also non-idiomatic: a FooError type can typically describe something, else it would be an ErrFoo variable.

変更の背景

このコミットの主な背景は、Go言語の標準ライブラリ net パッケージ内に存在していた InvalidConnErrorUnknownSocketError という2つのエラー型が、以下の理由により不要と判断されたためです。

  1. 未使用 (Unused): コードベースのどこからも参照されておらず、実際に使用されていない「デッドコード」でした。未使用のコードは、メンテナンスの負担を増やし、コードの理解を妨げ、将来的なバグの原因となる可能性があります。
  2. 未ドキュメント (Undocumented): これらのエラー型は公式にドキュメント化されていませんでした。ドキュメントがない型は、その存在意義や使用方法が不明瞭であり、ライブラリの利用者にとって混乱を招く可能性があります。
  3. 非イディオム的 (Non-idiomatic): 特に InvalidConnError については、Go言語のエラーハンドリングの慣習(イディオム)に沿っていないと指摘されています。Goでは、特定の状態を表すエラーは ErrFoo のように Err プレフィックスを持つ変数として定義されることが一般的です。一方、FooError のような型は、より複雑なエラー情報(エラーコード、追加データなど)をカプセル化するために使用されます。InvalidConnError は単に「invalid Conn」という文字列を返すだけであり、カスタムエラー型として定義するほどの複雑性を持っていなかったため、Goのイディオムに反していました。

これらの理由から、コードベースの健全性を保ち、Goのエラーハンドリングのベストプラクティスに準拠するために、これらのエラー型を削除することが決定されました。

前提知識の解説

このコミットを理解するためには、Go言語におけるエラーハンドリングの基本的な考え方とイディオムについて理解しておく必要があります。

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

Go言語では、例外処理の代わりにエラーを「値」として扱います。関数がエラーを返す可能性がある場合、その関数の最後の戻り値として error 型を返します。エラーが発生しなかった場合は nil を返します。

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

// 呼び出し側
result, err := doSomething()
if err != nil {
    // エラー処理
    fmt.Println("Error:", err)
    return
}
// 成功時の処理
fmt.Println("Success:", result)

この if err != nil というパターンは、Goのエラーハンドリングの最も基本的な形であり、非常に頻繁に登場します。

error インターフェース

Goの error 型は、実際には以下のように定義された組み込みのインターフェースです。

type error interface {
    Error() string
}

つまり、Error() string メソッドを持つ任意の型は error インターフェースを満たし、エラーとして扱うことができます。

イディオム的なエラー型

Goでは、エラーを表現する方法にいくつかのイディオムがあります。

  1. シンプルなエラー (errors.New または fmt.Errorf):

    • errors.New("エラーメッセージ"): 静的なエラーメッセージを持つシンプルなエラーを作成します。
    • fmt.Errorf("フォーマット文字列 %v", 値): フォーマットされたメッセージを持つエラーを作成します。動的な情報を含める場合によく使われます。
  2. センチネルエラー (Sentinel Errors):

    • 特定の、期待されるエラー条件を示すために、パッケージレベルで定義される事前定義されたエラー変数です。
    • 通常、Err プレフィックスを付けて命名されます(例: var ErrNotFound = errors.New("item not found"))。
    • これらのエラーは、errors.Is 関数(Go 1.13以降)を使って比較されます。
    var ErrPermissionDenied = errors.New("permission denied")
    
    func checkAccess() error {
        // ...
        return ErrPermissionDenied
    }
    
    // 呼び出し側
    err := checkAccess()
    if errors.Is(err, ErrPermissionDenied) {
        fmt.Println("アクセスが拒否されました。")
    }
    
  3. カスタムエラー型 (Custom Error Types):

    • より複雑なエラー情報(エラーコード、タイムスタンプ、特定の詳細データなど)をカプセル化する必要がある場合に、error インターフェースを実装する独自の struct を定義します。
    • このコミットで削除された InvalidConnErrorUnknownSocketError は、このカスタムエラー型に該当します。しかし、これらは単に文字列を返すだけであり、追加の情報を保持していなかったため、カスタム型として定義するメリットが薄いと判断されました。
    type MyCustomError struct {
        Code    int
        Message string
        Op      string // 失敗した操作
    }
    
    func (e *MyCustomError) Error() string {
        return fmt.Sprintf("operation %s failed with code %d: %s", e.Op, e.Code, e.Message)
    }
    
    func performOperation() error {
        // ...
        return &MyCustomError{Code: 500, Message: "internal server error", Op: "performOperation"}
    }
    
    // 呼び出し側
    err := performOperation()
    var myErr *MyCustomError
    if errors.As(err, &myErr) { // errors.Asを使ってカスタムエラー型に変換
        fmt.Printf("カスタムエラー: コード=%d, メッセージ=%s\n", myErr.Code, myErr.Message)
    }
    

FooError vs ErrFoo

コミットメッセージで言及されている「FooError type can typically describe something, else it would be an ErrFoo variable」という点は、Goのエラーハンドリングにおける重要な慣習です。

  • ErrFoo (変数): これは、特定の、かつシンプルなエラー条件を示すためのセンチネルエラー(errors.New で作成される)として使用されます。例えば、io.EOFos.ErrNotExist などがこれに該当します。これらは、エラーが発生した理由が単一かつ明確な場合に用いられます。

  • FooError (型): これは、エラーが単なる文字列メッセージ以上の情報(エラーコード、発生時刻、関連するデータなど)を保持する必要がある場合に、カスタムエラー型として定義されます。例えば、ネットワークエラーの詳細な情報を持つ net.OpError などがこれに該当します。

InvalidConnError は、単に「invalid Conn」というメッセージを返すだけで、追加の情報を何も持っていませんでした。そのため、Goのイディオムに照らし合わせると、カスタムエラー型 InvalidConnError として定義するのではなく、var ErrInvalidConn = errors.New("invalid Conn") のようなセンチネルエラー変数 ErrInvalidConn として定義する方が適切である、という判断がなされたと考えられます。

技術的詳細

このコミットは、Go言語の net パッケージから InvalidConnErrorUnknownSocketError という2つのカスタムエラー型を削除することで、コードベースの整理とGoのエラーハンドリングイディオムへの準拠を目的としています。

InvalidConnError の問題点

InvalidConnErrorsrc/pkg/net/fd.gosrc/pkg/net/fd_windows.go の両方に定義されていました。その定義は以下の通りです。

type InvalidConnError struct{}

func (e *InvalidConnError) Error() string   { return "invalid Conn" }
func (e *InvalidConnError) Temporary() bool { return false }
func (e *InvalidConnError) Timeout() bool   { return false }

この型は、error インターフェースだけでなく、net パッケージ内で定義されている Temporary()Timeout() メソッドを持つインターフェース(おそらく net.Error インターフェース)も実装していました。しかし、コミットメッセージが指摘するように、この型は単に「invalid Conn」という固定文字列を返すだけであり、エラーに関する追加のコンテキストや情報を提供していませんでした。

Goのエラーハンドリングのイディオムでは、このようなシンプルなエラーは通常、errors.New を使用したセンチネルエラー変数(例: var ErrInvalidConn = errors.New("invalid Conn"))として表現されます。カスタムエラー型は、エラーがより複雑な状態やデータを持つ場合に予約されるべきです。InvalidConnError はその要件を満たしていなかったため、非イディオム的と判断されました。

UnknownSocketError の問題点

UnknownSocketErrorsrc/pkg/net/sock.go に定義されていました。その定義は以下の通りです。

type UnknownSocketError struct {
	sa syscall.Sockaddr
}

func (e *UnknownSocketError) Error() string {
	return "unknown socket address type " + reflect.TypeOf(e.sa).String()
}

このエラー型は syscall.Sockaddr というフィールドを持っており、エラーメッセージにその型情報を含めることで、InvalidConnError よりもわずかに動的な情報を提供していました。しかし、コミットメッセージではこの型も「unused and undocumented」とされており、実際にコードベースのどこからも使用されていなかったため、削除の対象となりました。未使用のコードは、たとえそれが潜在的に有用な情報を含んでいたとしても、コードベースの肥大化とメンテナンスコストの増加につながります。

削除の意義

これらのエラー型を削除することの意義は以下の通りです。

  1. コードベースの簡素化: 未使用のコードを削除することで、コードベースがスリムになり、理解しやすくなります。開発者は、実際に使用されているコードに集中できます。
  2. メンテナンスコストの削減: 未使用のコードは、将来的にバグの原因となったり、依存関係の更新時に問題を引き起こしたりする可能性があります。これらを削除することで、長期的なメンテナンスコストを削減できます。
  3. イディオムへの準拠: InvalidConnError の削除は、Goのエラーハンドリングのベストプラクティスとイディオムへの準拠を強化します。これにより、Goのコードベース全体の一貫性が向上し、新しい開発者がコードを理解しやすくなります。
  4. ドキュメントの整合性: 未ドキュメントの型を削除することで、ドキュメントと実際のコードベースとの間に不整合が生じる可能性がなくなります。

このコミットは、Go言語の標準ライブラリが常にクリーンで、効率的で、イディオムに沿ったものであることを保証するための継続的な努力の一環と言えます。

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

このコミットでは、以下の3つのファイルから合計21行のコードが削除されています。

  1. src/pkg/net/fd.go:

    • InvalidConnError 型の定義とそのメソッド(Error(), Temporary(), Timeout())が削除されました。
    --- a/src/pkg/net/fd.go
    +++ b/src/pkg/net/fd.go
    @@ -43,12 +43,6 @@ type netFD struct {
     	ncr, ncw int
     }
    
    -type InvalidConnError struct{}
    -
    -func (e *InvalidConnError) Error() string   { return "invalid Conn" }
    -func (e *InvalidConnError) Temporary() bool { return false }
    -func (e *e.InvalidConnError) Timeout() bool   { return false }
    -
     // A pollServer helps FDs determine when to retry a non-blocking
     // read or write after they get EAGAIN.  When an FD needs to wait,
     // send the fd on s.cr (for a read) or s.cw (for a write) to pass the
    
  2. src/pkg/net/fd_windows.go:

    • fd.go と同様に、Windows固有のファイルからも InvalidConnError 型の定義とそのメソッドが削除されました。
    --- a/src/pkg/net/fd_windows.go
    +++ b/src/pkg/net/fd_windows.go
    @@ -14,12 +14,6 @@ import (
     	"unsafe"
     )
    
    -type InvalidConnError struct{}
    -
    -func (e *InvalidConnError) Error() string   { return "invalid Conn" }
    -func (e *InvalidConnError) Temporary() bool { return false }
    -func (e *InvalidConnError) Timeout() bool   { return false }
    -
     var initErr error
    
     func init() {
    
  3. src/pkg/net/sock.go:

    • UnknownSocketError 型の定義とそのメソッド(Error())が削除されました。
    • また、UnknownSocketError の定義で使用されていた reflect パッケージのインポートも不要になったため削除されました。
    --- a/src/pkg/net/sock.go
    +++ b/src/pkg/net/sock.go
    @@ -10,7 +10,6 @@ package net
    
     import (
     	"io"
    -	"reflect"
     	"syscall"
     )
    
    @@ -70,14 +69,6 @@ func socket(net string, f, t, p int, la, ra syscall.Sockaddr, toAddr func(syscal
     	return fd, nil
     }
    
    -type UnknownSocketError struct {
    -	sa syscall.Sockaddr
    -}
    -
    -func (e *UnknownSocketError) Error() string {
    -	return "unknown socket address type " + reflect.TypeOf(e.sa).String()
    -}
    -
     type writerOnly struct {
     	io.Writer
     }
    

コアとなるコードの解説

このコミットのコアとなる変更は、Goの net パッケージから特定のカスタムエラー型を削除することです。

InvalidConnError の削除

InvalidConnError は、net パッケージ内の fd.gofd_windows.go の両方に定義されていました。これは、ネットワーク接続が不正な状態であることを示すために意図されたエラー型でした。しかし、その実装は非常にシンプルで、Error() メソッドは常に "invalid Conn" という固定文字列を返すだけでした。また、Temporary()Timeout() メソッドも常に false を返していました。

Goのエラーハンドリングの慣習では、このように追加情報を持たないシンプルなエラーは、通常、errors.New を使って定義されるパッケージレベルの変数(センチネルエラー)として扱われます。例えば、var ErrInvalidConn = errors.New("invalid Conn") のように定義されるべきでした。カスタムエラー型として定義する意味が薄く、Goのイディオムに沿っていなかったため、削除されました。これにより、コードベースの冗長性が減り、Goのエラーハンドリングのベストプラクティスに準拠する形になりました。

UnknownSocketError の削除

UnknownSocketErrornet/sock.go に定義されていました。このエラー型は、syscall.Sockaddr というソケットアドレスの情報を保持し、Error() メソッドでその型情報をエラーメッセージに含めることで、より詳細な情報を提供しようとしていました。

しかし、コミットメッセージが明確に述べているように、この型は「未使用 (unused)」でした。つまり、このエラー型が実際にどこかのコードで生成されたり、チェックされたりすることはなかったということです。未使用のコードは、コードベースの肥大化を招き、開発者がコードを理解する際のノイズとなります。また、UnknownSocketError の定義のために reflect パッケージがインポートされていましたが、この型が削除されたことで reflect のインポートも不要となり、依存関係が一つ減りました。

変更の全体的な影響

これらのエラー型が削除されたことによる、net パッケージの外部からの動作上の影響はほとんどありません。なぜなら、これらの型は元々「未使用」であり、外部に公開されていても実際に利用されることがなかったためです。

この変更は、Goの標準ライブラリの内部的な品質向上と、Go言語のエラーハンドリングの設計思想へのより厳密な準拠を目的としています。未使用のコードを削除し、イディオムに沿わない設計を修正することで、コードベースの健全性が保たれ、将来的な開発とメンテナンスが容易になります。

関連リンク

参考にした情報源リンク