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

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

コミット

コミットハッシュ: fd34e78b53322114cfbcfa0af886a5a82a2f9ae5 作成者: Russ Cox rsc@golang.org 作成日: 2011年11月13日 22:42:42 -0500 コミットメッセージ: various: reduce overuse of os.EINVAL + others

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

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

元コミット内容

このコミットでは、Go言語の標準パッケージ全体で汎用的なエラー値(特にos.EINVAL)の過度な使用を減らし、より具体的で意味のあるエラーメッセージに置き換える大規模なリファクタリングが行われました。

主な変更箇所(15ファイル、36行追加、42行削除)

  1. bufio/bufio_test.go: os.EPIPEio.ErrClosedPipe
  2. compress/lzw/reader.go: os.EINVALerrClosed(新規定義)
  3. compress/lzw/writer.go: os.EINVALerrClosed(新規定義)
  4. compress/lzw/writer_test.go: os.EPIPEエラーハンドリングを削除
  5. compress/zlib/writer_test.go: os.EPIPEエラーハンドリングを削除
  6. crypto/rand/util.go: os.EINVALerrors.New("crypto/rand: prime size must be positive")
  7. crypto/tls/conn.go: os.EAGAINの言及をnet.Errorに変更
  8. encoding/xml/xml_test.go: os.EINVALpanic("unreachable")
  9. image/tiff/buffer.go: os.EINVALio.ErrUnexpectedEOF
  10. log/syslog/syslog.go: os.EINVALerrors.New("log/syslog: invalid priority")
  11. mime/multipart/formdata.go: os.EINVALio.ErrUnexpectedEOF
  12. net/http/httputil/persist.go: os.EBADFerrClosed(新規定義)
  13. net/http/transport.go: os.EINVALの使用を削除
  14. text/tabwriter/tabwriter.go: os.EIOio.ErrShortWrite
  15. websocket/websocket.go: os.EINVALerrSetTimeout(新規定義)

変更の背景

2011年のGo言語開発初期段階において、多くのパッケージで汎用的なOS由来のエラー値(os.EINVALos.EPIPEos.EBADFなど)が過度に使用されていました。これらのエラーは本来UNIXシステムコールの結果を表すものであり、Go言語の高レベルな抽象化には適していませんでした。

Russ Coxによるこの変更は、Goのエラー処理哲学を明確にし、より具体的で意味のあるエラーメッセージを提供することを目的としています。

前提知識の解説

UNIX errno とは

UNIX系システムでは、システムコールやライブラリ関数がエラーを返す際に、グローバル変数errnoに数値エラーコードを設定します。代表的なエラーコード:

  • EINVAL (22): 無効な引数
  • EPIPE (32): パイプの破綻
  • EBADF (9): 不正なファイル記述子
  • EIO (5): I/Oエラー
  • EAGAIN (11): リソースが一時的に利用不可

Go言語のエラー処理哲学

Go言語では2011年の設計段階から、以下の原則に基づいてエラー処理を行っています:

  1. エラーは値である: errorインターフェースを使用
  2. 明示的エラー処理: 例外ではなく戻り値でエラーを返す
  3. 文脈的エラーメッセージ: 何が起きたかを明確に説明
  4. コンテキストの追加: エラーをラップして情報を追加

技術的詳細

1. エラー値の分類と置換戦略

汎用エラーから具体的エラーへの変換パターン

パターン1: 新しい専用エラー変数の定義

// 変更前
func (d *decoder) Close() error {
    d.err = os.EINVAL // 汎用的すぎる
    return nil
}

// 変更後
var errClosed = errors.New("compress/lzw: reader/writer is closed")

func (d *decoder) Close() error {
    d.err = errClosed // 具体的で意味のある
    return nil
}

パターン2: 標準パッケージの適切なエラー値の使用

// 変更前
if int(off) >= len(r) || off < 0 {
    return 0, os.EINVAL
}

// 変更後
if int(off) >= len(r) || off < 0 {
    return 0, io.ErrUnexpectedEOF
}

パターン3: 即座に生成される説明的エラーメッセージ

// 変更前
if bits < 1 {
    err = os.EINVAL
}

// 変更後
if bits < 1 {
    err = errors.New("crypto/rand: prime size must be positive")
}

2. パッケージ別の詳細分析

compress/lzw パッケージ

LZW圧縮アルゴリズムの実装において、リーダー・ライター両方でerrClosedエラーを統一的に定義。これにより、クローズされたリーダー/ライターに対する操作を明確に識別可能。

crypto/rand パッケージ

暗号学的乱数生成において、「素数サイズは正の値でなければならない」という具体的な制約を明示的に表現。

net/http パッケージ

HTTP通信での接続状態管理において、errClosedを定義して接続がクローズされた状態を明確に表現。

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

1. compress/lzw/reader.go(53-74行目)

// 変更前
func (d *decoder) Close() error {
    d.err = os.EINVAL // in case any Reads come along
    return nil
}

// 変更後
var errClosed = errors.New("compress/lzw: reader/writer is closed")

func (d *decoder) Close() error {
    d.err = errClosed // in case any Reads come along
    return nil
}

2. crypto/rand/util.go(161-164行目)

// 変更前
func Prime(rand io.Reader, bits int) (p *big.Int, err error) {
    if bits < 1 {
        err = os.EINVAL
    }
    // ...
}

// 変更後
func Prime(rand io.Reader, bits int) (p *big.Int, err error) {
    if bits < 1 {
        err = errors.New("crypto/rand: prime size must be positive")
    }
    // ...
}

3. websocket/websocket.go(369-396行目)

// 変更前
func (ws *Conn) SetTimeout(nsec int64) error {
    if conn, ok := ws.rwc.(net.Conn); ok {
        return conn.SetTimeout(nsec)
    }
    return os.EINVAL
}

// 変更後
var errSetTimeout = errors.New("websocket: cannot set timeout: not using a net.Conn")

func (ws *Conn) SetTimeout(nsec int64) error {
    if conn, ok := ws.rwc.(net.Conn); ok {
        return conn.SetTimeout(nsec)
    }
    return errSetTimeout
}

コアとなるコードの解説

エラー処理の哲学的変化

この変更の核心は、「何が起きたかを正確に伝える」 というGo言語のエラー処理哲学の実現です。

1. 意味的明確性の向上

  • os.EINVAL(「無効な引数」)→ errors.New("crypto/rand: prime size must be positive")(「素数サイズは正の値でなければならない」)
  • 開発者が具体的に何を修正すべきかを理解できる

2. 抽象化レベルの統一

  • OS固有のエラーコードではなく、Go言語の抽象化レベルに適したエラー表現を使用
  • プラットフォーム間の一貫性を保証

3. デバッグ効率の向上

  • エラーメッセージから問題の発生箇所と原因を特定しやすい
  • ログ解析やトラブルシューティングの効率化

パフォーマンスへの影響

この変更によるパフォーマンスへの影響は軽微です:

利点

  • より具体的なエラーメッセージによるデバッグ時間の短縮
  • エラーハンドリングコードの可読性向上

潜在的コスト

  • 新しいエラー値の定義によるわずかなメモリ使用量の増加
  • 文字列比較の場合のわずかな処理時間の増加

後方互換性の考慮

この変更は破壊的変更ではありませんが、エラー値の種類が変更されるため、エラー値の同等性チェックを行うコードに影響を与える可能性があります。

関連リンク

参考にした情報源リンク