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

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

このコミットは、Go言語の標準ライブラリ filepath.Walk 関数の戻り値のチェックを追加することで、エラーハンドリングを改善するものです。具体的には、misc/dist/bindist.go ファイル内の makeTar および makeZip 関数において、filepath.Walk の実行結果を適切に処理するように変更されています。

コミット

commit 0fd53d8be91e4f48666fce2e6ba98a39c285b84b
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Apr 3 12:33:22 2012 -0700

    misc/dist: check return value from filepath.Walk
    
    Doesn't fix any known issue. This bit me in some unrelated
    code and I thought of this tool.
    
    R=golang-dev, krautz, mikkel
    CC=golang-dev
    https://golang.org/cl/5976067

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

https://github.com/golang/go/commit/0fd53d8be91e4f48666fce2e6ba98a39c285b84b

元コミット内容

misc/dist: check return value from filepath.Walk

filepath.Walk の戻り値をチェックする。

既知の問題を修正するものではない。これは、関連性のないコードで私を悩ませたので、このツール(misc/dist パッケージ内のツールを指すと思われる)のことを思い出した。

変更の背景

このコミットは、特定の既知のバグを修正するものではなく、コードの堅牢性を向上させるための予防的な変更です。コミットメッセージによると、作者であるBrad Fitzpatrick氏が別のプロジェクトで filepath.Walk の戻り値をチェックしていなかったために問題に遭遇し、その経験から misc/dist ツールでも同様の潜在的な問題があることに気づいたため、この修正を行ったとされています。

filepath.Walk はファイルシステムを再帰的に走査するための非常に便利な関数ですが、そのコールバック関数(WalkFunc)がエラーを返した場合でも、filepath.Walk 自体がエラーを返さない限り、呼び出し元はそのエラーを検知できません。このコミットは、filepath.Walk が返す可能性のあるエラーを適切に捕捉し、上位の関数に伝播させることで、ファイルシステム走査中に発生した問題がサイレントに無視されることを防ぎ、より信頼性の高い処理を実現することを目的としています。

前提知識の解説

Go言語の filepath.Walk 関数

filepath.Walk はGo言語の path/filepath パッケージに含まれる関数で、指定されたディレクトリツリーを再帰的に走査するために使用されます。そのシグネチャは以下の通りです。

func Walk(root string, fn WalkFunc) error
  • root: 走査を開始するルートディレクトリのパス。
  • fn: WalkFunc 型の関数で、走査中に見つかった各ファイルやディレクトリに対して呼び出されます。

WalkFunc のシグネチャは以下の通りです。

type WalkFunc func(path string, info os.FileInfo, err error) error
  • path: 現在走査しているファイルまたはディレクトリのパス。
  • info: os.FileInfo インターフェースで、ファイルまたはディレクトリのメタデータ(名前、サイズ、パーミッション、変更時刻など)を提供します。
  • err: filepath.Walkpath にアクセスしようとした際に発生したエラー。例えば、パーミッションエラーなど。

WalkFunc の戻り値は、filepath.Walk の動作を制御します。

  • nil を返すと、走査は続行されます。
  • filepath.SkipDir を返すと、現在のディレクトリの残りのエントリと、そのディレクトリのサブディレクトリの走査がスキップされます。これは、特定のディレクトリ(例: .git ディレクトリ)を無視したい場合などに便利です。
  • nil でも filepath.SkipDir でもないエラーを返すと、filepath.Walk はそのエラーを呼び出し元に返し、走査を停止します。

エラーハンドリングの重要性

Go言語では、エラーは戻り値として明示的に扱われることが一般的です。関数がエラーを返す可能性がある場合、呼び出し元はそのエラーをチェックし、適切に処理する責任があります。filepath.Walk の場合、WalkFunc 内で発生したエラーを WalkFunc が返しても、filepath.Walk 自体がそのエラーを捕捉して呼び出し元に返さない限り、エラーは無視されてしまいます。

このコミットの変更前は、filepath.Walk の戻り値がチェックされていなかったため、ファイルシステム走査中に何らかの問題(例: ファイルの読み取りエラー、パーミッションエラーなど)が発生しても、それが上位の処理に伝わらず、サイレントに失敗する可能性がありました。これは、特にアーカイブ作成のような重要な操作においては、データの欠損や不完全なアーカイブが生成される原因となり得ます。

技術的詳細

このコミットの主要な変更点は、filepath.Walk 関数の呼び出し方法と、その戻り値の処理方法です。

変更前は、filepath.Walk は以下のように呼び出されていました。

filepath.Walk(workdir, filepath.WalkFunc(func(path string, fi os.FileInfo, err error) error {
    // ... 処理 ...
}))

この形式では、filepath.Walk の戻り値(error 型)が変数に代入されず、そのまま破棄されていました。そのため、filepath.Walk の内部でエラーが発生し、それが filepath.Walk の戻り値として返されても、呼び出し元ではそのエラーを検知できませんでした。

変更後は、filepath.Walk の戻り値が err 変数に代入され、その errnil でない場合に、上位の関数にエラーを伝播させるように修正されています。

err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
    // ... 処理 ...
})
if err != nil {
    return err
}

この変更により、filepath.Walk がファイルシステム走査中にエラーを検出した場合、そのエラーが makeTarmakeZip 関数に適切に伝達され、これらの関数もエラーを返すようになります。これにより、アーカイブ作成プロセス中に発生した問題が適切に報告され、デバッグやエラー回復が容易になります。

また、filepath.WalkFunc の型キャスト filepath.WalkFunc(...) が省略されていますが、これはGoの関数リテラルが適切なインターフェースを満たす場合に自動的に型推論されるため、機能的な違いはありません。コードの簡潔さを保つための変更です。

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

変更は misc/dist/bindist.go ファイルの2箇所にあります。

makeTar 関数内

--- a/misc/dist/bindist.go
+++ b/misc/dist/bindist.go
@@ -556,7 +556,7 @@ func makeTar(targ, workdir string) error {
 	zout := gzip.NewWriter(f)
 	tw := tar.NewWriter(zout)
 
-	filepath.Walk(workdir, filepath.WalkFunc(func(path string, fi os.FileInfo, err error) error {
+	err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
 		if !strings.HasPrefix(path, workdir) {
 			log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
 		}
@@ -605,8 +605,10 @@ func makeTar(targ, workdir string) error {
 		defer r.Close()
 		_, err = io.Copy(tw, r)
 		return err
-	}))
-
+	})
+	if err != nil {
+		return err
+	}
 	if err := tw.Close(); err != nil {
 		return err
 	}

makeZip 関数内

--- a/misc/dist/bindist.go
+++ b/misc/dist/bindist.go
@@ -623,7 +625,7 @@ func makeZip(targ, workdir string) error {
 	}
 	zw := zip.NewWriter(f)
 
-	filepath.Walk(workdir, filepath.WalkFunc(func(path string, fi os.FileInfo, err error) error {
+	err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
 		if fi.IsDir() {
 			return nil
 		}
@@ -664,8 +666,10 @@ func makeZip(targ, workdir string) error {
 		defer r.Close()
 		_, err = io.Copy(w, r)
 		return err
-	}))
-
+	})
+	if err != nil {
+		return err
+	}
 	if err := zw.Close(); err != nil {
 		return err
 	}

コアとなるコードの解説

上記の変更箇所では、makeTar 関数と makeZip 関数がそれぞれアーカイブ(tarballとzipファイル)を作成する際に、filepath.Walk を使用して指定された作業ディレクトリ (workdir) 内のファイルを走査しています。

変更前は、filepath.Walk の呼び出しが単独で行われ、その戻り値が無視されていました。これは、filepath.Walk がファイルシステム走査中にエラー(例: 読み取り権限がないファイルに遭遇した場合など)を検出してエラーを返しても、makeTarmakeZip 関数はそのエラーを知ることができないことを意味します。結果として、不完全なアーカイブが生成されたり、予期せぬ動作が発生したりする可能性がありました。

変更後は、filepath.Walk の呼び出し結果が err 変数に代入され、その直後に if err != nil { return err } というエラーチェックが追加されています。これにより、filepath.Walk がエラーを返した場合、そのエラーが即座に makeTar または makeZip 関数の呼び出し元に伝播されるようになります。

この修正は、Go言語におけるエラーハンドリングのベストプラクティスに従ったものであり、プログラムの堅牢性と信頼性を向上させます。ファイルシステム操作は外部要因(パーミッション、ディスク容量、ファイル破損など)によって失敗する可能性が高いため、このようなエラーチェックは特に重要です。

関連リンク

参考にした情報源リンク