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

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

このコミットは、Go言語の標準ライブラリ image/png パッケージ内のテストファイル reader_test.go における変更です。具体的には、テストコード内でファイルの読み込みに使用されていた bufio.Readerbufio.Scanner に置き換えられ、それに伴い読み込みロジックが修正されています。

コミット

commit c6f23bb7c1cdc83cea7e0cec5c912a67d1b19988
Author: Rob Pike <r@golang.org>
Date:   Wed Feb 20 15:57:18 2013 -0800

    image/png: use Scanner in reader_test.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/7399044

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

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

元コミット内容

image/png: use Scanner in reader_test.

このコミットは、image/png パッケージのテストコード reader_test.go において、bufio.Reader の代わりに bufio.Scanner を使用するように変更するものです。

変更の背景

この変更の背景には、Go言語の標準ライブラリにおけるI/O処理のベストプラクティスの進化があります。bufio.Reader は汎用的なバッファリングリーダーであり、ReadString などのメソッドを使って区切り文字(この場合は改行文字 \n)まで読み込むことができます。しかし、bufio.Scanner は、より高レベルで効率的なテキストスキャン(行単位、単語単位など)のために設計されています。

テストコード reader_test.go では、SNG (Simple Network Graphics) フォーマットのデータを生成し、それを読み込んで比較する処理が行われています。SNGフォーマットはテキストベースであり、行ごとにデータを処理することが一般的です。このような行指向の処理において、bufio.Scannerbufio.Reader よりも簡潔で、エラーハンドリングも容易になるという利点があります。

具体的には、bufio.Reader.ReadString はエラーが発生した場合に部分的な文字列とエラーを返す可能性がありますが、bufio.ScannerScan() メソッドが false を返した後に Err() メソッドでエラーを確認するという、より明確なエラー報告メカニズムを提供します。これにより、テストコードの堅牢性と可読性が向上します。

前提知識の解説

Go言語の bufio パッケージ

bufio パッケージは、Go言語におけるバッファリングされたI/O操作を提供します。これにより、ディスクI/OやネットワークI/Oなどの低レベルなI/O操作の効率を向上させることができます。主な型として ReaderScanner があります。

  • bufio.Reader:

    • io.Reader インターフェースをラップし、バッファリング機能を追加します。
    • バイト単位、ルーン単位、行単位など、様々な粒度での読み込みメソッドを提供します。
    • ReadString(delim byte): 指定された区切り文字が出現するまでデータを読み込み、その文字列を返します。区切り文字も返される文字列に含まれます。エラーが発生した場合、部分的なデータとエラーを返すことがあります。
    • ReadBytes(delim byte): ReadString と同様ですが、バイトスライスを返します。
  • bufio.Scanner:

    • io.Reader からデータを読み込み、指定された「スキャン関数」に基づいてトークン(行、単語など)に分割するためのユーティリティです。
    • Scan(): 次のトークン(デフォルトでは次の行)を読み込みます。読み込みが成功した場合は true を、エラーが発生したかEOFに達した場合は false を返します。
    • Text(): Scan()true を返した場合に、現在スキャンされたトークン(文字列)を返します。
    • Bytes(): Scan()true を返した場合に、現在スキャンされたトークン(バイトスライス)を返します。
    • Err(): Scan()false を返した場合に、発生したエラーを返します。EOFの場合は nil を返します。
    • SetSplitFunc(split SplitFunc): トークンの分割方法をカスタマイズできます。

image/png パッケージ

Go言語の標準ライブラリ image/png パッケージは、PNG (Portable Network Graphics) 画像フォーマットのエンコードとデコードをサポートします。このパッケージは、PNGファイルの読み込み(デコード)と書き込み(エンコード)のための機能を提供し、image パッケージの Image インターフェースと連携して動作します。

テストにおけるSNGフォーマット

SNG (Simple Network Graphics) は、PNG画像をテキスト形式で表現するためのシンプルなフォーマットです。これは、PNGのバイナリデータを直接扱うのではなく、人間が読みやすいテキスト形式で画像データを記述することを目的としています。テストにおいては、SNGフォーマットを使用することで、PNG画像の生成と検証をより容易に行うことができます。テストコードでは、生成されたPNG画像をSNG形式に変換し、期待されるSNG出力と比較することで、エンコーダ/デコーダの正確性を検証しています。

技術的詳細

このコミットの主要な変更点は、reader_test.go 内の TestReader 関数における bufio.Reader から bufio.Scanner への移行です。

元のコードでは、bufio.NewReader を使用して io.Pipe やファイルからデータを読み込み、pb.ReadString('\n')sb.ReadString('\n') を使って行単位でデータを取得していました。この ReadString メソッドは、改行文字が見つかるか、エラーが発生するか、EOFに達するまで読み込みを続けます。エラーハンドリングは、perr != nilserr != nil で直接エラーをチェックする形で行われていました。

変更後のコードでは、bufio.NewScanner を使用しています。Scanner のループは pdone := pb.Scan()sdone := sb.Scan() で制御されます。Scan() メソッドは、次のトークン(デフォルトでは行)が正常に読み込まれた場合に true を返します。ループの終了条件は、両方のスキャナが false を返した場合(つまり、両方ともEOFに達したかエラーが発生した場合)です。

エラーハンドリングも変更されています。Scanner では、Scan()false を返した後に Err() メソッドを呼び出すことで、読み込み中に発生したエラーを確認します。EOFの場合は Err()nil を返します。これにより、EOFと実際のエラーを明確に区別できるようになります。

また、行の取得は pb.Text()sb.Text() を使用して行われます。これは Scan() が成功した後に呼び出され、スキャンされた行の文字列を返します。

この変更により、行単位の読み込みとエラーハンドリングのロジックがよりGo言語のイディオムに沿った形になり、コードの可読性と堅牢性が向上しています。特に、bufio.Scanner は内部でバッファリングを効率的に管理し、ReadString のように部分的な結果とエラーを同時に返すような複雑さを避けることができます。

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

変更は src/pkg/image/png/reader_test.go ファイルの TestReader 関数に集中しています。

--- a/src/pkg/image/png/reader_test.go
+++ b/src/pkg/image/png/reader_test.go
@@ -208,7 +208,7 @@ func TestReader(t *testing.T) {
 		}
 
 		piper, pipew := io.Pipe()
-		pb := bufio.NewReader(piper)
+		pb := bufio.NewScanner(piper)
 		go sng(pipew, fn, img)
 		defer piper.Close()
 
@@ -219,7 +219,7 @@ func TestReader(t *testing.T) {
 			continue
 		}
 		defer sf.Close()
-		sb := bufio.NewReader(sf)
+		sb := bufio.NewScanner(sf)
 		if err != nil {
 			t.Error(fn, err)
 			continue
@@ -227,24 +227,28 @@ func TestReader(t *testing.T) {
 
 		// Compare the two, in SNG format, line by line.
 		for {
-\t\t\tps, perr := pb.ReadString('\n')
-\t\t\tss, serr := sb.ReadString('\n')
-\t\t\tif perr == io.EOF && serr == io.EOF {
+\t\t\tpdone := pb.Scan()
+\t\t\tsdone := sb.Scan()
+\t\t\tif pdone && sdone {
 \t\t\t\tbreak
 \t\t\t}\n-\t\t\tif perr != nil {\n-\t\t\t\tt.Error(fn, perr)\n-\t\t\t\tbreak\n-\t\t\t}\n-\t\t\tif serr != nil {\n-\t\t\t\tt.Error(fn, serr)\n+\t\t\tif pdone || sdone {
+\t\t\t\tt.Errorf("%s: Different sizes", fn)
 \t\t\t\tbreak
 \t\t\t}\n+\t\t\tps := pb.Text()
+\t\t\tss := sb.Text()
 \t\t\tif ps != ss {\
 \t\t\t\tt.Errorf("%s: Mismatch\\n%sversus\\n%s\\n", fn, ps, ss)\
 \t\t\t\tbreak
 \t\t\t}\
 \t\t}
+\t\tif pb.Err() != nil {
+\t\t\tt.Error(fn, pb.Err())
+\t\t}\
+\t\tif sb.Err() != nil {
+\t\t\tt.Error(fn, sb.Err())
+\t\t}\
 \t}\
 }

コアとなるコードの解説

  1. bufio.NewReader から bufio.NewScanner への変更:

    • pb := bufio.NewReader(piper)pb := bufio.NewScanner(piper) に変更されました。
    • sb := bufio.NewReader(sf)sb := bufio.NewScanner(sf) に変更されました。 これにより、行単位の読み込みに特化した Scanner の機能を利用できるようになります。
  2. 読み込みループの変更:

    • 変更前: ps, perr := pb.ReadString('\n')ss, serr := sb.ReadString('\n') を使って行を読み込み、perrserr でエラーやEOFを直接チェックしていました。
    • 変更後: pdone := pb.Scan()sdone := sb.Scan() を使って次の行の読み込みを試みます。Scan() は読み込みが成功したかどうかを示すブール値を返します。
    • ループの終了条件も変更され、if pdone && sdone で両方のスキャナがまだ読み込み可能であればループを継続し、そうでなければ break します。
    • if pdone || sdone の条件は、片方のスキャナだけがEOFに達したかエラーになった場合(つまり、ファイルサイズが異なる場合)を検出するために追加されました。これは、テスト対象のSNGデータが同じサイズであることを期待しているためです。
  3. 行データの取得方法の変更:

    • 変更前: psss には ReadString が返した文字列が直接格納されていました。
    • 変更後: ps := pb.Text()ss := sb.Text() を使って、Scan() が成功した後にスキャンされた行の文字列を取得します。
  4. エラーハンドリングの変更:

    • 変更前は、perr != nilserr != nil でエラーを個別にチェックしていました。
    • 変更後、ループの後に if pb.Err() != nilif sb.Err() != nil を追加し、スキャナの処理中に発生したエラーをまとめてチェックするようにしました。ScannerErr() メソッドは、Scan()false を返した原因がEOFではなく実際のエラーであった場合に、そのエラーを返します。

これらの変更により、テストコードは bufio.Scanner の提供するよりクリーンで効率的な行処理のイディオムに準拠するようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev)
  • Go言語のソースコード (github.com/golang/go)
  • Go言語における bufio.Readerbufio.Scanner の使い分けに関する一般的な情報源(例: 技術ブログ、Stack Overflowなど)
  • PNG (Portable Network Graphics) フォーマットの一般的な知識
  • SNG (Simple Network Graphics) フォーマットに関する情報(テストコードの文脈から推測)