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

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

このコミットは、Go言語の標準ライブラリ archive/tar パッケージに、tarアーカイブの読み書きに関する具体的なコード例を追加するものです。これにより、ユーザーがこのパッケージをより簡単に理解し、利用できるようになります。既存のコメント形式の例は削除され、example_test.go という新しいファイルに、実行可能なテスト形式の例が追加されました。

コミット

commit 97916f11548110b282c460aa9f939bac139ca99c
Author: Robin Eklind <r.eklind.87@gmail.com>
Date:   Mon Feb 4 12:37:18 2013 +1100

    archive/tar: Add reader and writer code example.
    
    Remove the previous comment examples.
    
    R=golang-dev, minux.ma, adg
    CC=golang-dev
    https://golang.org/cl/7220048

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

https://github.com/golang/go/commit/97916f11548110b282c460aa9f939bac139ca99c

元コミット内容

このコミットの目的は、archive/tar パッケージにtarアーカイブの読み書きのコード例を追加することです。具体的には、以前のコメント形式の例を削除し、代わりに example_test.go という新しいファイルに、実行可能なテスト形式の例を追加しています。

変更の背景

Go言語の標準ライブラリでは、各パッケージの利用方法を示すために、Example 関数を用いたコード例を提供することが推奨されています。これらの Example 関数は、go test コマンドを実行する際に自動的にテストされ、その出力が期待される出力と一致するかどうかが検証されます。これにより、コード例が常に最新かつ正確であることが保証されます。

このコミット以前は、archive/tar パッケージの reader.gowriter.go ファイル内に、コメント形式で簡単な使用例が記述されていました。しかし、これらのコメント例は自動テストの対象外であり、コードの変更に伴って古くなったり、誤りを含んだりする可能性がありました。

このコミットの背景には、以下の目的があります。

  1. コード例の信頼性向上: Example 関数としてコード例を記述することで、go test による自動検証が可能になり、例が常に正しく動作することを保証します。
  2. ドキュメントの改善: go doc コマンドやGoの公式ドキュメントサイト(pkg.go.devなど)で、Example 関数が自動的に抽出され、パッケージのドキュメントとして表示されます。これにより、ユーザーはより簡単にパッケージの使用方法を学ぶことができます。
  3. 開発者の利便性向上: 開発者がパッケージの機能を理解し、利用する際の障壁を低減します。

前提知識の解説

Go言語の Example 関数

Go言語では、パッケージの使用例を示すために Example 関数という特別な関数を定義できます。これらの関数は、_test.go ファイル内に記述され、Example というプレフィックスを持つ必要があります。

例:

func ExamplePackageFunction() {
    // ... コード例 ...
    // Output:
    // 期待される出力
}

Example 関数の特徴は以下の通りです。

  • 自動テスト: go test コマンドを実行すると、Example 関数内のコードが実行され、その標準出力が // Output: コメントに記述された期待される出力と一致するかどうかが検証されます。これにより、コード例が常に正しく動作することが保証されます。
  • ドキュメント生成: go doc コマンドやGoの公式ドキュメントサイト(pkg.go.devなど)で、Example 関数が自動的に抽出され、パッケージのドキュメントとして表示されます。これにより、ユーザーはパッケージの利用方法を簡単に参照できます。
  • 可読性: コード例が独立した関数として記述されるため、可読性が向上します。

archive/tar パッケージ

archive/tar パッケージは、TAR(Tape Archive)形式のファイルを読み書きするためのGo言語の標準ライブラリです。TARファイルは、複数のファイルを一つのアーカイブにまとめるための一般的な形式であり、主にファイルのバックアップや配布に使用されます。

このパッケージの主要なコンポーネントは以下の通りです。

  • tar.Reader: TARアーカイブからファイルを読み取るための構造体です。Next() メソッドを使用してアーカイブ内の次のファイルに移動し、io.Reader インターフェースを実装しているため、ファイルの内容を読み取ることができます。
  • tar.Writer: TARアーカイブにファイルを書き込むための構造体です。WriteHeader() メソッドを使用して新しいファイルのヘッダーを書き込み、Write() メソッドを使用してファイルの内容を書き込みます。
  • tar.Header: TARアーカイブ内の各ファイルのエントリ(メタデータ)を表す構造体です。ファイル名、サイズ、パーミッション、タイムスタンプなどの情報が含まれます。

TARファイルの構造は、基本的に各ファイルのヘッダー(メタデータ)とそれに続くファイルデータが連続して配置される形式です。tar.Reader はこの構造を解析し、tar.Writer はこの構造を生成します。

技術的詳細

このコミットでは、archive/tar パッケージの Example 関数が example_test.go ファイルに新しく追加されました。この Example 関数は、tarアーカイブの作成(書き込み)と読み取りの両方のプロセスを包括的に示しています。

Tarアーカイブの作成(書き込み)

  1. bytes.Buffer の利用: bytes.Buffer を使用して、メモリ上にtarアーカイブを構築します。これにより、ファイルシステムに直接書き込むことなく、アーカイブの作成と読み取りの例を完結させることができます。
  2. tar.NewWriter: tar.NewWriter(buf) を呼び出して、bytes.Buffer に書き込むための tar.Writer インスタンスを作成します。
  3. ファイルの追加:
    • tar.Header 構造体を作成し、ファイル名 (Name) とファイルサイズ (Size) を設定します。
    • tw.WriteHeader(hdr) を呼び出して、ファイルのヘッダーをtarアーカイブに書き込みます。
    • tw.Write([]byte(file.Body)) を呼び出して、ファイルの内容をtarアーカイブに書き込みます。
    • このプロセスをループで繰り返し、複数のファイルをアーカイブに追加します。
  4. tw.Close(): すべてのファイルの書き込みが完了したら、tw.Close() を呼び出して tar.Writer を閉じます。これにより、必要なフッター情報がアーカイブに書き込まれ、アーカイブが正しく閉じられます。

Tarアーカイブの読み取り

  1. bytes.NewReader の利用: 作成した bytes.Buffer の内容を読み取るために、bytes.NewReader(buf.Bytes()) を使用して io.Reader インターフェースを満たすリーダーを作成します。
  2. tar.NewReader: tar.NewReader(r) を呼び出して、tarアーカイブを読み取るための tar.Reader インスタンスを作成します。
  3. ファイルの読み取り:
    • 無限ループ内で tr.Next() を呼び出します。このメソッドは、アーカイブ内の次のファイルのエントリ(tar.Header)を返します。
    • io.EOF エラーが返された場合、アーカイブの終わりに達したことを意味するため、ループを終了します。
    • それ以外のエラーが発生した場合は、エラーハンドリングを行います。
    • fmt.Printf("Contents of %s:\\n", hdr.Name) でファイル名を表示します。
    • io.Copy(os.Stdout, tr) を使用して、現在のファイルの内容を標準出力にコピーします。trio.Reader インターフェースを実装しているため、ファイルの内容を直接読み取ることができます。

既存コメントの削除

src/pkg/archive/tar/reader.gosrc/pkg/archive/tar/writer.go から、以前のコメント形式のコード例が削除されました。これは、新しい Example 関数がより信頼性が高く、ドキュメントとして優れているため、重複する情報が不要になったためです。

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

src/pkg/archive/tar/example_test.go (新規追加)

--- /dev/null
+++ b/src/pkg/archive/tar/example_test.go
@@ -0,0 +1,75 @@
+package tar_test
+
+import (
+	"archive/tar"
+	"bytes"
+	"fmt"
+	"io"
+	"log"
+	"os"
+)
+
+func Example() {
+	// Create a buffer to write our archive to.
+	buf := new(bytes.Buffer)
+
+	// Create a new tar archive.
+	tw := tar.NewWriter(buf)
+
+	// Add some files to the archive.
+	var files = []struct {
+		Name, Body string
+	}{
+		{"readme.txt", "This archive contains some text files."},
+		{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
+		{"todo.txt", "Get animal handling licence."},
+	}
+	for _, file := range files {
+		hdr := &tar.Header{
+			Name: file.Name,
+			Size: int64(len(file.Body)),
+		}
+		if err := tw.WriteHeader(hdr); err != nil {
+			log.Fatalln(err)
+		}
+		if _, err := tw.Write([]byte(file.Body)); err != nil {
+			log.Fatalln(err)
+		}
+	}
+	// Make sure to check the error on Close.
+	if err := tw.Close(); err != nil {
+		log.Fatalln(err)
+	}
+
+	// Open the tar archive for reading.
+	r := bytes.NewReader(buf.Bytes())
+	tr := tar.NewReader(r)
+
+	// Iterate through the files in the archive.
+	for {
+		hdr, err := tr.Next()
+		if err == io.EOF {
+			// end of tar archive
+			break
+		}
+		if err != nil {
+			log.Fatalln(err)
+		}
+		fmt.Printf("Contents of %s:\\n", hdr.Name)
+		if _, err := io.Copy(os.Stdout, tr); err != nil {
+			log.Fatalln(err)
+		}
+		fmt.Println()
+	}
+
+	// Output:
+	// Contents of readme.txt:
+	// This archive contains some text files.
+	// Contents of gopher.txt:
+	// Gopher names:
+	// George
+	// Geoffrey
+	// Gonzo
+	// Contents of todo.txt:
+	// Get animal handling licence.
+}

src/pkg/archive/tar/reader.go (コメント削除)

--- a/src/pkg/archive/tar/reader.go
+++ b/src/pkg/archive/tar/reader.go
@@ -25,20 +25,6 @@ var (
 // A tar archive consists of a sequence of files.
 // The Next method advances to the next file in the archive (including the first),
 // and then it can be treated as an io.Reader to access the file's data.
-//
-// Example:
-//	tr := tar.NewReader(r)
-//	for {
-//		hdr, err := tr.Next()
-//		if err == io.EOF {
-//			// end of tar archive
-//			break
-//		}
-//		if err != nil {
-//			// handle error
-//		}
-//		io.Copy(data, tr)
-//	}
 type Reader struct {
 	r   io.Reader
 	err error

src/pkg/archive/tar/writer.go (コメント削除)

--- a/src/pkg/archive/tar/writer.go
+++ b/src/pkg/archive/tar/writer.go
@@ -25,17 +25,6 @@ var (
 // A tar archive consists of a sequence of files.
 // Call WriteHeader to begin a new file, and then call Write to supply that file's data,
 // writing at most hdr.Size bytes in total.
-//
-// Example:
-//	tw := tar.NewWriter(w)
-//	hdr := new(tar.Header)
-//	hdr.Size = length of data in bytes
-//	// populate other hdr fields as desired
-//	if err := tw.WriteHeader(hdr); err != nil {
-//		// handle error
-//	}
-//	io.Copy(tw, data)
-//	tw.Close()
 type Writer struct {
 	w          io.Writer
 	err        error

コアとなるコードの解説

追加された Example 関数は、archive/tar パッケージの基本的な使用方法を簡潔かつ網羅的に示しています。

  1. パッケージのインポート:

    • archive/tar: tarアーカイブの読み書きに必要な主要パッケージ。
    • bytes: メモリ上でバイトスライスを扱うためのパッケージ。ここでは bytes.Buffer を使用して、tarアーカイブをメモリ上に構築しています。
    • fmt: フォーマットされたI/O(出力)を行うためのパッケージ。
    • io: I/Oプリミティブを提供するパッケージ。io.EOFio.Copy などを使用します。
    • log: エラーロギングのためのパッケージ。
    • os: オペレーティングシステム機能へのアクセスを提供するパッケージ。ここでは os.Stdout を使用して標準出力に書き出しています。
  2. Example() 関数:

    • この関数は、go test 実行時に自動的にテストされ、その出力が // Output: コメントに記述された期待される出力と一致するかどうかが検証されます。
    • アーカイブの作成部分:
      • buf := new(bytes.Buffer): tarアーカイブのデータを一時的に保持するためのバッファを作成します。
      • tw := tar.NewWriter(buf): buf に書き込むための tar.Writer を作成します。
      • files スライスには、アーカイブに追加するファイルの名前と内容が定義されています。
      • ループ内で各ファイルについて以下の処理を行います。
        • hdr := &tar.Header{Name: file.Name, Size: int64(len(file.Body))}: tar.Header を作成し、ファイル名とサイズを設定します。
        • tw.WriteHeader(hdr): ヘッダーを書き込みます。
        • tw.Write([]byte(file.Body)): ファイルの内容を書き込みます。
      • tw.Close(): tar.Writer を閉じ、アーカイブを完成させます。エラーチェックは重要です。
    • アーカイブの読み取り部分:
      • r := bytes.NewReader(buf.Bytes()): buf に書き込まれたアーカイブデータを読み取るための io.Reader を作成します。
      • tr := tar.NewReader(r): r から読み取るための tar.Reader を作成します。
      • ループ内で各ファイルについて以下の処理を行います。
        • hdr, err := tr.Next(): 次のファイルのエントリ(ヘッダー)を読み込みます。
        • if err == io.EOF { break }: io.EOF はアーカイブの終端を示します。
        • if err != nil { log.Fatalln(err) }: その他のエラーは致命的として処理します。
        • fmt.Printf("Contents of %s:\\n", hdr.Name): ファイル名を出力します。
        • io.Copy(os.Stdout, tr): ファイルの内容を標準出力にコピーします。tr は現在のファイルの内容を読み取る io.Reader として機能します。
    • // Output: コメント: このコメントブロックは、Example 関数が実行されたときに期待される標準出力を定義します。go test はこの出力と実際の出力を比較して、テストの合否を判断します。

この例は、archive/tar パッケージの WriterReader の両方の基本的なワークフローを明確に示しており、Go言語の Example 関数のベストプラクティスに従っています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Go言語のテストに関する一般的な慣習とベストプラクティス