[インデックス 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.go と writer.go ファイル内に、コメント形式で簡単な使用例が記述されていました。しかし、これらのコメント例は自動テストの対象外であり、コードの変更に伴って古くなったり、誤りを含んだりする可能性がありました。
このコミットの背景には、以下の目的があります。
- コード例の信頼性向上:
Example関数としてコード例を記述することで、go testによる自動検証が可能になり、例が常に正しく動作することを保証します。 - ドキュメントの改善:
go docコマンドやGoの公式ドキュメントサイト(pkg.go.devなど)で、Example関数が自動的に抽出され、パッケージのドキュメントとして表示されます。これにより、ユーザーはより簡単にパッケージの使用方法を学ぶことができます。 - 開発者の利便性向上: 開発者がパッケージの機能を理解し、利用する際の障壁を低減します。
前提知識の解説
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アーカイブの作成(書き込み)
bytes.Bufferの利用:bytes.Bufferを使用して、メモリ上にtarアーカイブを構築します。これにより、ファイルシステムに直接書き込むことなく、アーカイブの作成と読み取りの例を完結させることができます。tar.NewWriter:tar.NewWriter(buf)を呼び出して、bytes.Bufferに書き込むためのtar.Writerインスタンスを作成します。- ファイルの追加:
tar.Header構造体を作成し、ファイル名 (Name) とファイルサイズ (Size) を設定します。tw.WriteHeader(hdr)を呼び出して、ファイルのヘッダーをtarアーカイブに書き込みます。tw.Write([]byte(file.Body))を呼び出して、ファイルの内容をtarアーカイブに書き込みます。- このプロセスをループで繰り返し、複数のファイルをアーカイブに追加します。
tw.Close(): すべてのファイルの書き込みが完了したら、tw.Close()を呼び出してtar.Writerを閉じます。これにより、必要なフッター情報がアーカイブに書き込まれ、アーカイブが正しく閉じられます。
Tarアーカイブの読み取り
bytes.NewReaderの利用: 作成したbytes.Bufferの内容を読み取るために、bytes.NewReader(buf.Bytes())を使用してio.Readerインターフェースを満たすリーダーを作成します。tar.NewReader:tar.NewReader(r)を呼び出して、tarアーカイブを読み取るためのtar.Readerインスタンスを作成します。- ファイルの読み取り:
- 無限ループ内で
tr.Next()を呼び出します。このメソッドは、アーカイブ内の次のファイルのエントリ(tar.Header)を返します。 io.EOFエラーが返された場合、アーカイブの終わりに達したことを意味するため、ループを終了します。- それ以外のエラーが発生した場合は、エラーハンドリングを行います。
fmt.Printf("Contents of %s:\\n", hdr.Name)でファイル名を表示します。io.Copy(os.Stdout, tr)を使用して、現在のファイルの内容を標準出力にコピーします。trはio.Readerインターフェースを実装しているため、ファイルの内容を直接読み取ることができます。
- 無限ループ内で
既存コメントの削除
src/pkg/archive/tar/reader.go と src/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 パッケージの基本的な使用方法を簡潔かつ網羅的に示しています。
-
パッケージのインポート:
archive/tar: tarアーカイブの読み書きに必要な主要パッケージ。bytes: メモリ上でバイトスライスを扱うためのパッケージ。ここではbytes.Bufferを使用して、tarアーカイブをメモリ上に構築しています。fmt: フォーマットされたI/O(出力)を行うためのパッケージ。io: I/Oプリミティブを提供するパッケージ。io.EOFやio.Copyなどを使用します。log: エラーロギングのためのパッケージ。os: オペレーティングシステム機能へのアクセスを提供するパッケージ。ここではos.Stdoutを使用して標準出力に書き出しています。
-
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 パッケージの Writer と Reader の両方の基本的なワークフローを明確に示しており、Go言語の Example 関数のベストプラクティスに従っています。
関連リンク
- Go言語の
archive/tarパッケージ公式ドキュメント: https://pkg.go.dev/archive/tar - Go言語の
Example関数に関する公式ブログ記事(Testing with the Go command): https://go.dev/blog/testing (Example functionsのセクションを参照)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
- Go言語のテストに関する一般的な慣習とベストプラクティス