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

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

このコミットは、Go言語の標準ライブラリにおけるbufiocompress/gzipcompress/zlibパッケージのNewWriterXxx系の関数シグネチャの変更に対応するためのgo fixツールの修正と、関連ドキュメントの更新を含んでいます。特に、これらの関数がエラーを返さなくなった変更に既存のコードを適応させるための自動修正機能が追加されました。

コミット

commit da8f037b57241b0b84fab9d4c9e69b53e7118850
Author: Nigel Tao <nigeltao@golang.org>
Date:   Wed Feb 15 14:41:47 2012 +1100

    fix: add fix for bufio, gzip, zlib NewWriterXxx changes.
    
    Fixes #2979.
    
    R=rsc, r
    CC=golang-dev
    https://golang.org/cl/5664046

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

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

元コミット内容

bufiogzipzlibパッケージのNewWriterXxx系の関数変更に対する修正を追加。 Issue #2979を修正。

変更の背景

このコミットは、Go 1のリリースに向けたAPIのクリーンアップの一環として行われました。具体的には、bufiocompress/gzipcompress/zlibパッケージ内のNewWriterNewWriterSizeなどの関数が、以前はWriter, errorのような形式でエラーを返していたのに対し、エラーを返さないようにシグネチャが変更されました。

このような変更は、Go言語のAPI設計思想に基づいています。Goでは、エラーは明示的に処理されるべきですが、常にエラーが発生するとは限らない初期化関数などでは、エラーを返さない設計が好まれる場合があります。この変更により、これらの関数の呼び出し元で不要なエラーチェックが削減され、コードが簡潔になることが期待されます。

しかし、既存のコードベースでは、これらの関数がエラーを返すことを前提に記述されているため、コンパイルエラーが発生する可能性があります。この問題を解決するため、go fixツールに自動修正機能を追加し、開発者が容易にコードを新しいAPIに適応できるようにすることが目的です。

関連するコードレビューは以下の通りです。

  • http://codereview.appspot.com/5639057 (compress/* packages error handling during API cleanups for Go 1)
  • http://codereview.appspot.com/5642054 (bufio: NewReaderSize and NewWriterSize no longer return errors)

これらのコードレビューは、Go 1のAPI安定化プロセスにおいて、compressパッケージやbufioパッケージのNewWriter関連関数のエラー返却に関する変更が議論され、最終的にエラーを返さない形に落ち着いた経緯を示しています。

前提知識の解説

  • go fixツール: go fixはGo言語のツールチェーンに含まれるコマンドラインツールで、Go言語のAPI変更や言語仕様の変更に伴い、古いGoコードを新しいGoコードに自動的に書き換える機能を提供します。これにより、Go言語のバージョンアップに伴うコードの修正作業を大幅に軽減できます。
  • bufioパッケージ: bufioパッケージは、バッファリングされたI/O操作を実装するための機能を提供します。bufio.NewReaderbufio.NewWriterなどの関数は、io.Readerio.Writerをラップして、効率的な読み書きを可能にします。
  • compress/gzipパッケージ: compress/gzipパッケージは、gzip形式の圧縮データストリームを読み書きするための機能を提供します。gzip.NewWriterは、io.Writerをラップしてgzip圧縮データを書き込むための*gzip.Writerを返します。
  • compress/zlibパッケージ: compress/zlibパッケージは、zlib形式の圧縮データストリームを読み書きするための機能を提供します。zlib.NewWriterは、io.Writerをラップしてzlib圧縮データを書き込むための*zlib.Writerを返します。
  • Goのエラーハンドリング: Go言語では、関数が複数の値を返すことができ、慣習的に最後に戻り値としてエラーを返します。エラーがない場合はnilを返します。また、不要な戻り値は_(ブランク識別子)を使って破棄することができます。例えば、w, _ := gzip.NewWriter(writer)のように記述されます。

技術的詳細

このコミットの技術的な核心は、go fixツールがどのようにして古いAPI呼び出しを新しいAPI呼び出しに変換するかという点にあります。

  1. APIシグネチャの変更:

    • bufio.NewReaderSize(r io.Reader, size int) (*Reader, error) -> bufio.NewReaderSize(r io.Reader, size int) *Reader
    • bufio.NewWriterSize(w io.Writer, size int) (*Writer, error) -> bufio.NewWriterSize(w io.Writer, size int) *Writer
    • gzip.NewWriter(w io.Writer) (*Writer, error) -> gzip.NewWriter(w io.Writer) *Writer
    • zlib.NewWriter(w io.Writer) (*Writer, error) -> zlib.NewWriter(w io.Writer) *Writer
    • zlib.NewWriterDict(w io.Writer, level int, dict []byte) (*Writer, error) -> zlib.NewWriterLevelDict(w io.Writer, level int, dict []byte) (*Writer, error) (関数名も変更)
  2. 型名の変更:

    • gzip.Compressor -> gzip.Writer
    • gzip.Decompressor -> gzip.Reader
  3. go fixの動作: go fixツールは、Goの抽象構文木(AST: Abstract Syntax Tree)を解析し、特定のパターンに合致するコードを自動的に書き換えます。

    • エラー変数の削除: 変更前のAPIでは、w, _ := NewWriter(...)のように、2つの戻り値のうち2番目のエラー値を_で破棄しているケースが多く見られました。go fixは、このような代入文を検出し、エラーを返さなくなった新しいAPIに合わせてw := NewWriter(...)のように、_の部分を削除します。ただし、w, err := NewWriter(...)のようにエラー変数を明示的に宣言している場合は、go fixは自動修正を行わず、コンパイルエラーとして開発者に手動での修正を促します。これは、err変数が後続のコードで利用されている可能性があるため、go fixが安全に削除できないためです。
    • 型名の置換: gzip.Compressorgzip.Decompressorという古い型名が使用されている箇所を検出し、それぞれgzip.Writergzip.Readerに置換します。
    • 関数名の置換: zlib.NewWriterDictzlib.NewWriterLevelDictにリネームされたことに対応し、関数呼び出しを新しい名前に変更します。

この修正は、Go言語の進化において、APIの変更が既存のコードベースに与える影響を最小限に抑えるための重要なメカニズムであるgo fixツールの役割を明確に示しています。

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

このコミットでは、主に以下の4つのファイルが変更されています。

  1. doc/go1.html および doc/go1.tmpl: Go 1のリリースノートに相当するドキュメントファイルです。bufiocompress/flatecompress/gzipcompress/zlibパッケージの変更点について記述が追加・修正されています。特に、NewWriterXxx系の関数がエラーを返さなくなったこと、そしてgo fixツールがこれらの変更に対応することについて言及されています。また、go fixツールが警告を出すケースについても修正が加えられています。

  2. src/cmd/fix/newwriter.go (新規追加): go fixツールに新しい修正ルール「newWriter」を追加するGoソースファイルです。このファイルには、bufiocompress/gzipcompress/zlibパッケージのNewWriterXxx系の関数呼び出しと、gzip.Compressor/gzip.Decompressorの型名を自動的に修正するためのロジックが実装されています。

  3. src/cmd/fix/newwriter_test.go (新規追加): newwriter.goで実装されたgo fixの修正ルールをテストするためのGoソースファイルです。修正前と修正後のコードスニペットを定義し、go fixが期待通りにコードを変換するかどうかを検証します。

コアとなるコードの解説

src/cmd/fix/newwriter.goがこのコミットの主要な変更点です。

package main

import (
	"go/ast"
)

func init() {
	register(newWriterFix)
}

var newWriterFix = fix{
	"newWriter",
	"2012-02-14",
	newWriter,
	`Adapt bufio, gzip and zlib NewWriterXxx calls for whether they return errors.

Also rename gzip.Compressor and gzip.Decompressor to gzip.Writer and gzip.Reader.

http://codereview.appspot.com/5639057 and
http://codereview.appspot.com/5642054
`,
}

func newWriter(f *ast.File) bool {
	// 修正対象のパッケージがインポートされているか確認
	if !imports(f, "bufio") && !imports(f, "compress/gzip") && !imports(f, "compress/zlib") {
		return false // 関連パッケージがインポートされていなければ修正不要
	}

	fixed := false
	walk(f, func(n interface{}) {
		switch n := n.(type) {
		case *ast.SelectorExpr: // セレクタ式 (例: gzip.Compressor) の処理
			if isTopName(n.X, "gzip") {
				switch n.Sel.String() {
				case "Compressor":
					n.Sel = &ast.Ident{Name: "Writer"} // gzip.Compressor -> gzip.Writer
					fixed = true
				case "Decompressor":
					n.Sel = &ast.Ident{Name: "Reader"} // gzip.Decompressor -> gzip.Reader
					fixed = true
				}
			} else if isTopName(n.X, "zlib") {
				if n.Sel.String() == "NewWriterDict" {
					n.Sel = &ast.Ident{Name: "NewWriterLevelDict"} // zlib.NewWriterDict -> zlib.NewWriterLevelDict
					fixed = true
				}
			}

		case *ast.AssignStmt: // 代入文 (例: w, _ = gzip.NewWriter(w)) の処理
			// 2つの左辺値と1つの右辺値を持つ代入文を対象とする
			if len(n.Lhs) != 2 || len(n.Rhs) != 1 {
				return
			}
			// 2番目の左辺値がブランク識別子 '_' であるか確認
			i, ok := n.Lhs[1].(*ast.Ident)
			if !ok {
				return
			}
			if i.String() != "_" {
				return // '_' でなければ修正しない (例: w, err := ...)
			}
			// 右辺値が関数呼び出しであるか確認
			c, ok := n.Rhs[0].(*ast.CallExpr)
			if !ok {
				return
			}
			// 関数呼び出しがセレクタ式 (例: gzip.NewWriter) であるか確認
			s, ok := c.Fun.(*ast.SelectorExpr)
			if !ok {
				return
			}
			sel := s.Sel.String()
			switch {
			// bufio.NewReaderSize または bufio.NewWriterSize の呼び出し
			case isTopName(s.X, "bufio") && (sel == "NewReaderSize" || sel == "NewWriterSize"):
				// No-op. (ここでは特に何もしないが、後続の処理でLhsを修正する)
			// gzip.NewWriter の呼び出し
			case isTopName(s.X, "gzip") && sel == "NewWriter":
				// No-op.
			// zlib.NewWriter の呼び出し
			case isTopName(s.X, "zlib") && sel == "NewWriter":
				// No-op.
			default:
				return // 上記以外の関数呼び出しは修正しない
			}
			// 左辺値から2番目の要素 (エラーを破棄していた '_') を削除
			n.Lhs = n.Lhs[:1]
			fixed = true
		}
	})
	return fixed
}

このコードは、Goのgo/astパッケージを使用してソースコードの抽象構文木を走査し、特定のパターンに合致するノードを修正します。

  • newWriter関数: go fixの修正ロジック本体です。
    • まず、対象のファイルがbufiocompress/gzipcompress/zlibのいずれかのパッケージをインポートしているかを確認します。インポートしていなければ、修正の必要がないため処理を終了します。
    • walk関数(go fixフレームワークの一部)を使ってASTを再帰的に走査します。
    • *ast.SelectorExprの処理: これはpackage.Nameのような形式の式を表します。
      • gzip.Compressorgzip.Decompressorといった型名が使用されている場合、それぞれgzip.Writergzip.Readerに書き換えます。
      • zlib.NewWriterDictが使用されている場合、zlib.NewWriterLevelDictに書き換えます。
    • *ast.AssignStmtの処理: これは代入文を表します。
      • w, _ = functionCall()のように、2つの左辺値(変数とブランク識別子_)と1つの右辺値(関数呼び出し)を持つ代入文を検出します。
      • 右辺値の関数呼び出しがbufio.NewReaderSizebufio.NewWriterSizegzip.NewWriterzlib.NewWriterのいずれかである場合、左辺値からブランク識別子_を削除し、w = functionCall()のように1つの左辺値のみを持つ代入文に変換します。これにより、エラーを返さなくなった新しいAPIシグネチャに対応します。

src/cmd/fix/newwriter_test.goは、このnewWriter関数の動作を検証するためのテストケースを定義しています。特に、bw, err := bufio.NewWriterSize(w, 256)のようにエラー変数を明示的に宣言しているケースはgo fixが修正しないこと(Unfixableとコメントされている)もテストケースで確認されています。

関連リンク

参考にした情報源リンク