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

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

このコミットは、Go言語の標準ライブラリである encoding/hex パッケージ内の hex.go ファイルに対する変更です。encoding/hex パッケージは、バイトスライスと16進数文字列の間でエンコードおよびデコードを行う機能を提供します。具体的には、dumper 型の Write メソッドにおいて、書き込み操作のエラーチェックが追加されています。

コミット

commit 01d49dc2ddda443f302be8ca0aa1378d550687ef
Author: Rui Ueyama <ruiu@google.com>
Date:   Mon Mar 17 12:07:30 2014 -0700

    encoding/hex: add error check for write error.
    
    I believe the original author of this code just forgot to check for error here.
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/76760043

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

https://github.com/golang/go/commit/01d49dc2ddda443f302be8ca0aa1378d550687ef

元コミット内容

--- a/src/pkg/encoding/hex/hex.go
+++ b/src/pkg/encoding/hex/hex.go
@@ -146,6 +146,9 @@ func (h *dumper) Write(data []byte) (n int, err error) {
 		th.buf[12] = ' '
 		th.buf[13] = ' '
 		t_, err = h.w.Write(h.buf[4:])
+		tif err != nil {
+		treturn
+		t}
 		t}
 		tEncode(h.buf[:], data[i:i+1])
 		th.buf[2] = ' '

変更の背景

このコミットの背景には、Go言語における堅牢なエラーハンドリングの原則があります。Goでは、エラーは例外ではなく、通常の戻り値として扱われます。関数がエラーを返す可能性がある場合、呼び出し元はそのエラーを明示的にチェックし、適切に処理することが期待されます。

encoding/hex パッケージの dumper 型の Write メソッドは、内部的に io.Writer インターフェースを満たす h.w に対してデータを書き込んでいます。この書き込み操作は、ディスクの空き容量不足、ネットワークの問題、権限の問題など、様々な理由で失敗する可能性があります。元のコードでは、h.w.Write(h.buf[4:]) の呼び出し結果として返されるエラーがチェックされていませんでした。

エラーチェックが欠落していると、Write 操作が失敗した場合でも、関数は成功したかのように処理を続行してしまいます。これにより、データが正しく書き込まれないにもかかわらず、呼び出し元にはその事実が伝わらず、サイレントなデータ破損や予期せぬ動作につながる可能性があります。このコミットは、このような潜在的な問題を修正し、Goのエラーハンドリングのベストプラクティスに準拠させることを目的としています。コミットメッセージにある「I believe the original author of this code just forgot to check for error here.」という記述は、このエラーチェックの欠落が単なる見落としであったことを示唆しています。

前提知識の解説

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

Go言語では、エラーは error インターフェースを実装する型として扱われます。関数がエラーを返す可能性がある場合、その関数の戻り値の最後の要素として error 型が宣言されます。慣例として、エラーがない場合は nil が返され、エラーがある場合は nil ではない error 値が返されます。

func someFunction() (resultType, error) {
    // ... 処理 ...
    if somethingWentWrong {
        return zeroValue, errors.New("something went wrong")
    }
    return actualResult, nil
}

呼び出し元は、if err != nil のパターンを使用してエラーをチェックし、適切なエラー処理ロジックを実行します。

result, err := someFunction()
if err != nil {
    // エラー処理
    log.Printf("Error: %v", err)
    return
}
// 成功時の処理

この明示的なエラーチェックは、Goプログラムの堅牢性を高める上で非常に重要です。

io.Writer インターフェース

io.Writer はGo言語の標準ライブラリ io パッケージで定義されているインターフェースです。データを書き込むための単一のメソッド Write を持ちます。

type Writer interface {
    Write(p []byte) (n int, err error)
}

Write メソッドは、p からデータを書き込み、書き込まれたバイト数 n と、書き込み中に発生したエラー err を返します。Write メソッドを実装する型は、ファイル、ネットワーク接続、バッファなど、様々な出力先を表すことができます。このインターフェースは、GoにおけるI/O操作の抽象化と統一された処理を可能にします。

encoding/hex パッケージと dumper

encoding/hex パッケージは、バイナリデータを16進数表現に、またはその逆変換を行う機能を提供します。このパッケージには、Dump 関数や NewDecoderNewEncoder といった関数が含まれています。

このコミットで変更された dumper 型は、hex.Dump 関数が内部で使用する型です。Dump 関数は、与えられたバイトスライスを hexdump -C コマンドのような形式で16進数ダンプ文字列に変換します。dumper 型は io.Writer インターフェースを実装しており、内部の h.w フィールドを通じて実際の書き込みを行います。この h.w は、Dump 関数が内部で利用する bytes.Buffer など、任意の io.Writer 実装が設定されます。

技術的詳細

変更が加えられたのは、src/pkg/encoding/hex/hex.go ファイル内の dumper 型の Write メソッドです。

dumper 型は、io.Writer インターフェースを実装しており、Write メソッドを通じてデータを処理し、整形された16進数ダンプを内部の io.Writer (h.w) に書き込みます。

Write メソッドの関連する部分の元のコードは以下のようになっていました。

func (h *dumper) Write(data []byte) (n int, err error) {
    // ... (省略) ...
    if h.n == 0 { // 行の先頭の場合
        // ... (アドレスやヘッダの書き込み) ...
        _, err = h.w.Write(h.buf[4:]) // ここで書き込み
    }
    // ... (省略) ...
}

ここで、h.w.Write(h.buf[4:])io.WriterWrite メソッドを呼び出しています。このメソッドは、書き込まれたバイト数と error を返します。元のコードでは、返された err 変数にエラーが代入されていましたが、その直後に if err != nil のチェックが行われていませんでした。

Go言語の慣例では、Write メソッドのようなI/O操作はエラーを返す可能性が高いため、その戻り値のエラーを常にチェックすることが推奨されます。エラーをチェックしないと、下層の io.Writer が書き込みに失敗した場合でも、dumper.Write メソッドはエラーを返さずに処理を続行してしまいます。これは、呼び出し元が書き込みの失敗を検知できないことを意味し、アプリケーションの動作に予期せぬ影響を与える可能性があります。

このコミットは、この見落としを修正し、h.w.Write の呼び出し後にエラーをチェックするロジックを追加することで、dumper.Write メソッドが下層の書き込みエラーを適切に伝播するようにしています。

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

変更は src/pkg/encoding/hex/hex.go ファイルの dumper 型の Write メソッド内、148行目から150行目にかけての3行の追加です。

--- a/src/pkg/encoding/hex/hex.go
+++ b/src/pkg/encoding/hex/hex.go
@@ -146,6 +146,9 @@ func (h *dumper) Write(data []byte) (n int, err error) {
 		t		h.buf[12] = ' '
 		t		h.buf[13] = ' '
 		t		_, err = h.w.Write(h.buf[4:])
+		t		if err != nil {
+		t			return
+		t		}
 		t	}
 		t	Encode(h.buf[:], data[i:i+1])
 		t	h.buf[2] = ' '

具体的には、以下の3行が追加されました。

			if err != nil {
				return
			}

コアとなるコードの解説

追加されたコードは、h.w.Write(h.buf[4:]) の呼び出し直後に配置されています。

  1. if err != nil {

    • これは、直前の h.w.Write 呼び出しで返された err 変数が nil ではない(つまりエラーが発生した)かどうかをチェックするGo言語の標準的なエラーハンドリングパターンです。
  2. return

    • もし errnil でなかった場合、この return ステートメントが実行されます。
    • dumper.Write メソッドのシグネチャは (n int, err error) です。この return は、現在の n の値(この時点ではまだ初期値の 0 か、それまでに書き込まれたバイト数)と、h.w.Write から返されたエラー err をそのまま呼び出し元に返します。
    • これにより、下層の io.Writer で発生した書き込みエラーが、dumper.Write メソッドの呼び出し元に適切に伝播されるようになります。

この変更により、dumper.Write メソッドは、内部の書き込み操作が失敗した場合にそのエラーを即座に検知し、呼び出し元に報告するようになります。これは、プログラムの堅牢性を向上させ、予期せぬI/Oエラーがサイレントに無視されることを防ぐ上で非常に重要です。Goのエラーハンドリングの哲学に沿った、シンプルかつ効果的な修正と言えます。

関連リンク

参考にした情報源リンク