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

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

このコミットは、Go言語の標準ライブラリであるbufioパッケージ内のNewReaderSize関数とNewWriterSize関数のインターフェース変更に関するものです。具体的には、これらの関数がエラーを返さないように変更され、無効なバッファサイズが指定された場合でも内部で調整されるようになりました。これにより、これらの関数の利用が簡素化され、呼び出し側でのエラーハンドリングが不要になります。

コミット

commit bb7b1a11d57066078fe841d1b324770faaf1ad45
Author: Rob Pike <r@golang.org>
Date:   Wed Feb 8 13:07:13 2012 +1100

    bufio: drop error return for NewReaderSize and NewWriterSize
    It complicates the interface unnecessarily.
    Document this in go1.html.
    Also update the go/doc Makefile.
    
    Fixes #2836.
    
    R=golang-dev, gri, bradfitz
    CC=golang-dev
    https://golang.org/cl/5642054

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

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

元コミット内容

bufio: drop error return for NewReaderSize and NewWriterSize It complicates the interface unnecessarily. Document this in go1.html. Also update the go/doc Makefile.

Fixes #2836.

変更の背景

この変更の主な背景は、bufio.NewReaderSizeおよびbufio.NewWriterSize関数のインターフェースの簡素化です。以前は、これらの関数は指定されたバッファサイズが無効な場合にエラーを返していました。しかし、コミットメッセージにあるように「インターフェースを不必要に複雑にしている」という判断がなされました。

Go言語の設計哲学の一つに「エラーは明示的に扱うべきだが、不必要なエラーハンドリングは避けるべき」という考え方があります。このケースでは、無効なバッファサイズが指定されたとしても、ライブラリ側で適切な最小サイズに調整することで、エラーを返す必要がなくなり、呼び出し側のコードをよりシンプルに保つことができると判断されました。

また、この変更はGo 1のリリースに向けたものであり、Go 1の互換性に関するドキュメント(go1.html)にもこの変更が記載されることになりました。これは、Go 1が安定版としてリリースされるにあたり、APIの安定性と使いやすさを向上させるための取り組みの一環です。Fixes #2836という記述がありますが、このIssueの具体的な内容は公開されていません。しかし、コミットメッセージから、このIssueがインターフェースの複雑さやエラーハンドリングの改善に関連するものであったと推測できます。

前提知識の解説

Go言語のbufioパッケージ

bufioパッケージは、Go言語の標準ライブラリの一部であり、I/O操作をバッファリングするための機能を提供します。バッファリングは、ディスクI/OやネットワークI/Oなどの低速な操作の回数を減らすことで、プログラムのパフォーマンスを向上させるために使用されます。

  • bufio.Reader: io.Readerインターフェースをラップし、バッファリングされた読み取り操作を提供します。これにより、一度に大量のデータを読み込み、そのバッファから少しずつデータを消費することができます。
  • bufio.Writer: io.Writerインターフェースをラップし、バッファリングされた書き込み操作を提供します。これにより、小さな書き込みをまとめて一度に基になるio.Writerにフラッシュすることができます。
  • NewReaderSize(rd io.Reader, size int) (*Reader, error) (変更前): 指定されたio.Readerとバッファサイズで新しいReaderを作成します。sizeが小さすぎる場合や無効な場合にエラーを返していました。
  • NewWriterSize(wr io.Writer, size int) (*Writer, error) (変更前): 指定されたio.Writerとバッファサイズで新しいWriterを作成します。sizeが0以下の場合にエラーを返していました。

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

Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値としてerror型の値を返します。nilはエラーがないことを意味し、非nilの値はエラーが発生したことを示します。

このコミット以前は、NewReaderSizeNewWriterSizeはエラーを返していました。これは、呼び出し側がバッファサイズの妥当性をチェックし、エラーが発生した場合にはそれに応じた処理を行う必要があったことを意味します。

バッファサイズの妥当性

バッファリングを行う際には、適切なバッファサイズを選択することが重要です。

  • 小さすぎるバッファ: バッファリングの恩恵が少なくなり、I/O操作の回数が増えてパフォーマンスが低下する可能性があります。
  • 大きすぎるバッファ: メモリを不必要に消費する可能性があります。

しかし、多くのアプリケーションでは、バッファサイズが厳密に特定の値を満たす必要はなく、ある程度の範囲内であれば問題ありません。このコミットの変更は、ユーザーが指定したサイズが推奨される最小サイズを下回る場合でも、ライブラリが自動的に適切なサイズに調整することで、ユーザーがエラーを意識する必要をなくすというアプローチを取っています。

技術的詳細

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

  1. NewReaderSizeおよびNewWriterSize関数のシグネチャ変更:

    • 変更前: func NewReaderSize(rd io.Reader, size int) (*Reader, error)
    • 変更後: func NewReaderSize(rd io.Reader, size int) *Reader
    • 同様にNewWriterSizeもエラー戻り値が削除されました。 これにより、これらの関数を呼び出す側では、エラーをチェックするための2番目の戻り値を受け取る必要がなくなりました。
  2. 無効なバッファサイズの内部処理:

    • NewReaderSizeでは、sizeminReadBufferSize(16バイト)より小さい場合、sizeminReadBufferSizeに調整するようになりました。
    • NewWriterSizeでは、sizeが0以下の場合、sizedefaultBufSize(4096バイト)に調整するようになりました。 これにより、ユーザーが無効なバッファサイズを指定しても、関数はパニックを起こしたりエラーを返したりせず、内部で適切なサイズにフォールバックして動作を継続します。
  3. 関連するエラー型とエラー処理の削除:

    • BufSizeErrorというカスタムエラー型が削除されました。これは、バッファサイズに関するエラーを返す必要がなくなったためです。
    • NewReaderおよびNewWriter関数内で、NewReaderSizeNewWriterSizeの呼び出し結果のエラーをチェックし、パニックを起こしていた部分が削除されました。これは、これらの関数がエラーを返さなくなったため、不要になりました。

これらの変更により、bufioパッケージの利用者は、バッファサイズの指定に関してより柔軟になり、エラーハンドリングの負担が軽減されます。特に、NewReaderNewWriterのようにデフォルトサイズを使用する関数は、内部でNewReaderSizeNewWriterSizeを呼び出す際にエラーチェックが不要になり、コードが簡潔になりました。

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

このコミットで最も重要な変更は、src/pkg/bufio/bufio.goファイル内のNewReaderSizeNewWriterSize関数の定義と実装です。

src/pkg/bufio/bufio.go

--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -11,7 +11,6 @@ import (
 	"bytes"
 	"errors"
 	"io"
-	"strconv"
 	"unicode/utf8"
 )
 
@@ -27,13 +26,6 @@ var (
 	errInternal          = errors.New("bufio: internal error")
 )
 
-// BufSizeError is the error representing an invalid buffer size.
-type BufSizeError int
-
-func (b BufSizeError) Error() string {
-	return "bufio: bad buffer size " + strconv.Itoa(int(b))
-}
-
 // Buffered input.
 
 // Reader implements buffering for an io.Reader object.
@@ -48,35 +40,29 @@ type Reader struct {
 
 const minReadBufferSize = 16
 
-// NewReaderSize creates a new Reader whose buffer has the specified size,
-// which must be at least 16 bytes.  If the argument io.Reader is already a
-// Reader with large enough size, it returns the underlying Reader.\n-// It returns the Reader and any error.
-func NewReaderSize(rd io.Reader, size int) (*Reader, error) {
--	if size < minReadBufferSize {
--		return nil, BufSizeError(size)
--	}
+// NewReaderSize returns a new Reader whose buffer has at least the specified
+// size. If the argument io.Reader is already a Reader with large enough
+// size, it returns the underlying Reader.
+func NewReaderSize(rd io.Reader, size int) *Reader {
 	// Is it already a Reader?
 	b, ok := rd.(*Reader)
 	if ok && len(b.buf) >= size {
--		return b, nil
++		return b
 	}
-+	if size < minReadBufferSize {
-+		size = minReadBufferSize
-+	}
-+	return &Reader{
-+		buf:          make([]byte, size),
-+		rd:           rd,
-+		lastByte:     -1,
-+		lastRuneSize: -1,
- 	}
--	b = new(Reader)
--	b.buf = make([]byte, size)
--	b.rd = rd
--	b.lastByte = -1
--	b.lastRuneSize = -1
--	return b, nil
++	if size < minReadBufferSize {
++		size = minReadBufferSize
++	}
++	return &Reader{
++		buf:          make([]byte, size),
++		rd:           rd,
++		lastByte:     -1,
++		lastRuneSize: -1,
++	}
 }
 
 // NewReader returns a new Reader whose buffer has the default size.
 func NewReader(rd io.Reader) *Reader {
--	b, err := NewReaderSize(rd, defaultBufSize)
--	if err != nil {
--		// cannot happen - defaultBufSize is a valid size
--		panic(err)
--	}
--	return b
++	return NewReaderSize(rd, defaultBufSize)
 }
 
 // fill reads a new chunk into the buffer.
@@ -396,33 +382,27 @@ type Writer struct {
 	wr  io.Writer
 }
 
-// NewWriterSize creates a new Writer whose buffer has the specified size,
-// which must be greater than zero. If the argument io.Writer is already a
-// Writer with large enough size, it returns the underlying Writer.
-// It returns the Writer and any error.
-func NewWriterSize(wr io.Writer, size int) (*Writer, error) {
--	if size <= 0 {
--		return nil, BufSizeError(size)
--	}
+// NewWriterSize returns a new Writer whose buffer has at least the specified
+// size. If the argument io.Writer is already a Writer with large enough
+// size, it returns the underlying Writer.
+func NewWriterSize(wr io.Writer, size int) *Writer {
 	// Is it already a Writer?
 	b, ok := wr.(*Writer)
 	if ok && len(b.buf) >= size {
--		return b, nil
++		return b
 	}
-+	if size <= 0 {
-+		size = defaultBufSize
- 	}
--	b = new(Writer)
--	b.buf = make([]byte, size)
--	b.wr = wr
--	return b, nil
++	if size <= 0 {
++		size = defaultBufSize
++	}
++	b = new(Writer)
++	b.buf = make([]byte, size)
++	b.wr = wr
++	return b
 }
 
 // NewWriter returns a new Writer whose buffer has the default size.
 func NewWriter(wr io.Writer) *Writer {
--	b, err := NewWriterSize(wr, defaultBufSize)
--	if err != nil {
--		// cannot happen - defaultBufSize is valid size
--		panic(err)
--	}
--	return b
++	return NewWriterSize(wr, defaultBufSize)
 }
 
 // Flush writes any buffered data to the underlying io.Writer.

src/pkg/bufio/bufio_test.go

テストファイルでは、NewReaderSizeNewWriterSizeの呼び出し箇所で、エラー戻り値を受け取る部分が削除されています。

--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -161,7 +161,7 @@ func TestReader(t *testing.T) {
 					bufreader := bufreaders[j]
 					bufsize := bufsizes[k]
 					read := readmaker.fn(bytes.NewBufferString(text))
-					buf, _ := NewReaderSize(read, bufsize)
+					buf := NewReaderSize(read, bufsize)
 					s := bufreader.fn(buf)
 					if s != text {
 						t.Errorf("reader=%s fn=%s bufsize=%d want=%q got=%q",
@@ -379,18 +379,14 @@ func TestWriter(t *testing.T) {
 			// and that the data is correct.
 
 			w.Reset()
-			buf, e := NewWriterSize(w, bs)
+			buf := NewWriterSize(w, bs)
 			context := fmt.Sprintf("nwrite=%d bufsize=%d", nwrite, bs)
-			if e != nil {
-				t.Errorf("%s: NewWriterSize %d: %v", context, bs, e)
-				continue
-			}
 			n, e1 := buf.Write(data[0:nwrite)
 			if e1 != nil || n != nwrite {
 				t.Errorf("%s: buf.Write %d = %d, %v", context, nwrite, n, e1)
 				continue
 			}
-			if e = buf.Flush(); e != nil {
+			if e := buf.Flush(); e != nil {
 				t.Errorf("%s: buf.Flush = %v", context, e)
 			}
 
@@ -447,23 +443,14 @@ func TestWriteErrors(t *testing.T) {
 
 func TestNewReaderSizeIdempotent(t *testing.T) {
 	const BufSize = 1000
-	b, err := NewReaderSize(bytes.NewBufferString("hello world"), BufSize)
-	if err != nil {
-		t.Error("NewReaderSize create fail", err)
-	}
+	b := NewReaderSize(bytes.NewBufferString("hello world"), BufSize)
 	// Does it recognize itself?
-	b1, err2 := NewReaderSize(b, BufSize)
-	if err2 != nil {
-		t.Error("NewReaderSize #2 create fail", err2)
-	}
+	b1 := NewReaderSize(b, BufSize)
 	if b1 != b {
 		t.Error("NewReaderSize did not detect underlying Reader")
 	}
 	// Does it wrap if existing buffer is too small?
-	b2, err3 := NewReaderSize(b, 2*BufSize)
-	if err3 != nil {
-		t.Error("NewReaderSize #3 create fail", err3)
-	}
+	b2 := NewReaderSize(b, 2*BufSize)
 	if b2 == b {
 		t.Error("NewReaderSize did not enlarge buffer")
 	}
@@ -471,23 +458,14 @@ func TestNewReaderSizeIdempotent(t *testing.T) {
 
 func TestNewWriterSizeIdempotent(t *testing.T) {
 	const BufSize = 1000
-	b, err := NewWriterSize(new(bytes.Buffer), BufSize)
-	if err != nil {
-		t.Error("NewWriterSize create fail", err)
-	}
+	b := NewWriterSize(new(bytes.Buffer), BufSize)
 	// Does it recognize itself?
-	b1, err2 := NewWriterSize(b, BufSize)
-	if err2 != nil {
-		t.Error("NewWriterSize #2 create fail", err2)
-	}
+	b1 := NewWriterSize(b, BufSize)
 	if b1 != b {
 		t.Error("NewWriterSize did not detect underlying Writer")
 	}
 	// Does it wrap if existing buffer is too small?
-	b2, err3 := NewWriterSize(b, 2*BufSize)
-	if err3 != nil {
-		t.Error("NewWriterSize #3 create fail", err3)
-	}
+	b2 := NewWriterSize(b, 2*BufSize)
 	if b2 == b {
 		t.Error("NewWriterSize did not enlarge buffer")
 	}
@@ -496,10 +474,7 @@ func TestNewWriterSizeIdempotent(t *testing.T) {
 func TestWriteString(t *testing.T) {
 	const BufSize = 8
 	buf := new(bytes.Buffer)
-	b, err := NewWriterSize(buf, BufSize)
-	if err != nil {
-		t.Error("NewWriterSize create fail", err)
-	}
+	b := NewWriterSize(buf, BufSize)
 	b.WriteString("0")                         // easy
 	b.WriteString("123456")                    // still easy
 	b.WriteString("7890")                      // easy after flush
@@ -516,10 +491,7 @@ func TestWriteString(t *testing.T) {
 
 func TestBufferFull(t *testing.T) {
 	const longString = "And now, hello, world! It is the time for all good men to come to the aid of their party"
-	buf, err := NewReaderSize(strings.NewReader(longString), minReadBufferSize)
-	if err != nil {
-		t.Fatal("NewReaderSize:", err)
-	}
+	buf := NewReaderSize(strings.NewReader(longString), minReadBufferSize)
 	line, err := buf.ReadSlice('!')
 	if string(line) != "And now, hello, " || err != ErrBufferFull {
 		t.Errorf("first ReadSlice(,) = %q, %v", line, err)
@@ -533,7 +505,7 @@ func TestBufferFull(t *testing.T) {
 func TestPeek(t *testing.T) {
 	p := make([]byte, 10)
 	// string is 16 (minReadBufferSize) long.
-	buf, _ := NewReaderSize(strings.NewReader("abcdefghijklmnop"), minReadBufferSize)
+	buf := NewReaderSize(strings.NewReader("abcdefghijklmnop"), minReadBufferSize)
 	if s, err := buf.Peek(1); string(s) != "a" || err != nil {
 		t.Fatalf("want %q got %q, err=%v", "a", string(s), err)
 	}
@@ -609,7 +581,7 @@ func testReadLine(t *testing.T, input []byte) {
 	for stride := 1; stride < 2; stride++ {
 		done := 0
 		reader := testReader{input, stride}
-		l, _ := NewReaderSize(&reader, len(input)+1)
+		l := NewReaderSize(&reader, len(input)+1)
 		for {
 			line, isPrefix, err := l.ReadLine()
 			if len(line) > 0 && err != nil {
@@ -646,7 +618,7 @@ func TestLineTooLong(t *testing.T) {
 		data = append(data, '0'+byte(i%10))
 	}
 	buf := bytes.NewBuffer(data)
-	l, _ := NewReaderSize(buf, minReadBufferSize)
+	l := NewReaderSize(buf, minReadBufferSize)
 	line, isPrefix, err := l.ReadLine()
 	if !isPrefix || !bytes.Equal(line, data[:minReadBufferSize]) || err != nil {
 		t.Errorf("bad result for first line: got %q want %q %v", line, data[:minReadBufferSize], err)
@@ -673,7 +645,7 @@ func TestReadAfterLines(t *testing.T) {
 	inbuf := bytes.NewBuffer([]byte(line1 + "\n" + restData))
 	outbuf := new(bytes.Buffer)
 	maxLineLength := len(line1) + len(restData)/2
-	l, _ := NewReaderSize(inbuf, maxLineLength)
+	l := NewReaderSize(inbuf, maxLineLength)
 	line, isPrefix, err := l.ReadLine()
 	if isPrefix || err != nil || string(line) != line1 {
 		t.Errorf("bad result for first line: isPrefix=%v err=%v line=%q", isPrefix, err, string(line))
@@ -688,7 +660,7 @@ func TestReadAfterLines(t *testing.T) {
 }
 
 func TestReadEmptyBuffer(t *testing.T) {
-	l, _ := NewReaderSize(new(bytes.Buffer), minReadBufferSize)
+	l := NewReaderSize(new(bytes.Buffer), minReadBufferSize)
 	line, isPrefix, err := l.ReadLine()
 	if err != io.EOF {
 		t.Errorf("expected EOF from ReadLine, got '%s' %t %s", line, isPrefix, err)
@@ -696,7 +668,7 @@ func TestReadEmptyBuffer(t *testing.T) {
 }
 
 func TestLinesAfterRead(t *testing.T) {
-	l, _ := NewReaderSize(bytes.NewBuffer([]byte("foo")), minReadBufferSize)
+	l := NewReaderSize(bytes.NewBuffer([]byte("foo")), minReadBufferSize)
 	_, err := ioutil.ReadAll(l)
 	if err != nil {
 		t.Error(err)
@@ -752,10 +724,7 @@ func TestReadLineNewlines(t *testing.T) {
 }
 
 func testReadLineNewlines(t *testing.T, input string, expect []readLineResult) {
-	b, err := NewReaderSize(strings.NewReader(input), minReadBufferSize)
-	if err != nil {
-		t.Fatal(err)
-	}
+	b := NewReaderSize(strings.NewReader(input), minReadBufferSize)
 	for i, e := range expect {
 		line, isPrefix, err := b.ReadLine()
 		if bytes.Compare(line, e.line) != 0 {

その他のファイル

  • doc/Makefile: tmpltohtmlのビルド方法が変更され、go buildコマンドが直接使われるようになりました。これは、Go 1のビルドシステムへの移行の一環と考えられます。
  • doc/go1.htmlおよびdoc/go1.tmpl: Go 1の互換性に関するドキュメントに、bufio.NewReaderSizebufio.NewWriterSizeがエラーを返さなくなったこと、および無効なサイズが調整されるようになったことが追記されました。これにより、Go 1への移行を検討している開発者がこの変更を認識できるようになります。
  • src/pkg/image/png/writer.go, src/pkg/net/http/cgi/host.go, src/pkg/net/http/fcgi/fcgi.go: これらのファイルでは、bufio.NewWriterSizebufio.NewReaderSizeの呼び出し箇所で、エラー戻り値を受け取る部分が削除されています。これは、API変更に伴う既存コードの修正です。

コアとなるコードの解説

NewReaderSize関数の変更

変更前は、NewReaderSizesize < minReadBufferSizeの場合にBufSizeErrorを返していました。変更後は、このエラーチェックとエラーの返却が削除され、代わりにsizeminReadBufferSizeより小さい場合にsize = minReadBufferSizeと内部で調整されるようになりました。

// 変更前
func NewReaderSize(rd io.Reader, size int) (*Reader, error) {
	if size < minReadBufferSize {
		return nil, BufSizeError(size) // エラーを返す
	}
	// ...
	return b, nil
}

// 変更後
func NewReaderSize(rd io.Reader, size int) *Reader {
	// ...
	if size < minReadBufferSize {
		size = minReadBufferSize // サイズを調整
	}
	return &Reader{
		buf:          make([]byte, size),
		rd:           rd,
		lastByte:     -1,
		lastRuneSize: -1,
	}
}

この変更により、NewReaderSizeの呼び出し側は、バッファサイズが小さすぎることを心配する必要がなくなりました。常に有効な*Readerが返されるため、エラーハンドリングのコードを記述する必要がなくなります。

NewWriterSize関数の変更

同様に、NewWriterSizesize <= 0の場合にBufSizeErrorを返していましたが、変更後はエラーを返さなくなり、sizeが0以下の場合にdefaultBufSizeに調整されるようになりました。

// 変更前
func NewWriterSize(wr io.Writer, size int) (*Writer, error) {
	if size <= 0 {
		return nil, BufSizeError(size) // エラーを返す
	}
	// ...
	return b, nil
}

// 変更後
func NewWriterSize(wr io.Writer, size int) *Writer {
	// ...
	if size <= 0 {
		size = defaultBufSize // サイズを調整
	}
	b = new(Writer)
	b.buf = make([]byte, size)
	b.wr = wr
	return b
}

この変更もNewReaderSizeと同様に、呼び出し側のコードを簡素化し、不必要なエラーハンドリングを排除することを目的としています。

NewReaderおよびNewWriter関数の変更

これらの関数は、内部でNewReaderSizeNewWriterSizeを呼び出していましたが、以前はエラー戻り値を受け取り、それがnilでない場合にパニックを起こしていました。エラー戻り値が削除されたことで、このパニック処理も不要になりました。

// 変更前 (NewReaderの例)
func NewReader(rd io.Reader) *Reader {
	b, err := NewReaderSize(rd, defaultBufSize)
	if err != nil {
		// cannot happen - defaultBufSize is a valid size
		panic(err)
	}
	return b
}

// 変更後 (NewReaderの例)
func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)
}

これにより、コードがより簡潔になり、defaultBufSizeが常に有効なサイズであることが保証されるため、不必要なエラーチェックが削除されました。

関連リンク

参考にした情報源リンク