[インデックス 1211] ファイルの概要
このコミットは、Go言語の初期開発段階において、テキスト整形のためのtabwriter
機能を個人の実験的なディレクトリ(usr/gri/pretty
)から標準ライブラリ(src/lib/tabwriter
)へ移動させ、より汎用的なio.Writer
インターフェースに準拠するように型名を変更し、初期のテストを追加したものです。これにより、tabwriter
はGo言語の標準的なパッケージとして利用可能になり、コードの再利用性と保守性が向上しました。
コミット
commit 01b44bbfc8ca90d9eb3418ad47d9d7a472bb4cde
Author: Robert Griesemer <gri@golang.org>
Date: Thu Nov 20 17:39:41 2008 -0800
- move tabwriter into library
- added preliminary tests (more to do)
- renamed type from TabWriter -> Writer
- adjusted my code where necessary
R=r
DELTA=825 (474 added, 346 deleted, 5 changed)
OCL=19744
CL=19753
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/01b44bbfc8ca90d9eb3418ad47d9d7a472bb4cde
元コミット内容
このコミットの目的は以下の通りです。
tabwriter
機能をライブラリとして移動する。- 予備的なテストを追加する(さらなる追加が必要)。
- 型名を
TabWriter
からWriter
へ変更する。 - 必要に応じて自身のコードを調整する。
変更の背景
このコミットは、Go言語の初期段階における標準ライブラリの整備の一環として行われました。tabwriter
は、テキストを整形し、タブ区切りのデータを列ごとに揃えるためのユーティリティです。元々は開発者個人の作業ディレクトリ(usr/gri/pretty
)に存在していましたが、その汎用性と有用性から、Go言語の標準ライブラリの一部として提供されるべきであると判断されました。
変更の主な背景は以下の点にあります。
- モジュール化と再利用性:
tabwriter
機能を独立したライブラリとして切り出すことで、他のGoプログラムから容易に利用できるようになります。これにより、コードの再利用性が高まり、Goエコシステム全体の発展に寄与します。 - 標準化と命名規則の遵守: Go言語では、
io.Writer
インターフェースを実装する型は、慣習的にWriter
という名前を持つことが推奨されます。既存のTabWriter
型をWriter
にリネームすることで、この命名規則に準拠し、Goの標準ライブラリとしての整合性を保ちます。 - 品質保証: ライブラリとして公開するにあたり、その機能が正しく動作することを保証するためのテストが不可欠です。このコミットでは、そのための予備的なテストが追加されました。
- 依存関係の整理:
Makefile
の変更は、新しいライブラリのビルドとクリーンアップのプロセスをGoのビルドシステムに統合し、依存関係を適切に管理するためのものです。
前提知識の解説
このコミットを理解するためには、以下のGo言語に関する基本的な知識が必要です。
-
Go言語のパッケージシステム: Go言語はパッケージ(
package
)によってコードを整理します。各パッケージは独立した名前空間を持ち、他のパッケージからインポートして利用できます。このコミットでは、tabwriter
が独立したパッケージとしてsrc/lib/tabwriter
に移動されました。 -
io.Writer
インターフェース: Go言語の標準ライブラリio
パッケージには、データを書き込むための汎用的なインターフェースWriter
が定義されています。これは、Write([]byte) (n int, err error)
メソッドを持つ型であれば、どのような型でもio.Writer
として扱うことができるというものです。tabwriter
がio.Writer
インターフェースを実装することで、ファイル、ネットワーク接続、バッファなど、様々な出力先に透過的にデータを書き込めるようになります。 -
Makefile
:Makefile
は、ソフトウェアのビルドプロセスを自動化するためのファイルです。Go言語の初期のプロジェクトでは、Makefile
がコンパイル、テスト、クリーンアップなどのタスクを管理するために広く使われていました。このコミットでは、tabwriter
ライブラリのビルドとインストールに関するルールがMakefile
に追加されています。 -
タブライター(TabWriter)の概念:
tabwriter
は、テキスト内のタブ区切りのデータを整形し、列を揃えるためのツールです。例えば、以下のようなタブ区切りのテキストがあったとします。Name Age City Alice 30 New York Bob 25 London Charlie 35 Paris
tabwriter
を使用すると、各列の幅を自動的に調整し、以下のようにきれいに揃えることができます。Name Age City Alice 30 New York Bob 25 London Charlie 35 Paris
これは、ドキュメントの整形やCLIツールの出力などで非常に有用です。
技術的詳細
このコミットにおける技術的な変更点は多岐にわたります。
-
ファイル移動とリネーム:
usr/gri/pretty/tabwriter.go
がsrc/lib/tabwriter/tabwriter.go
に移動されました。これは、tabwriter
が個人の実験的なコードからGoの標準ライブラリの一部へと昇格したことを意味します。usr/gri/pretty/Makefile
とusr/gri/pretty/untab.go
も変更され、tabwriter
への参照が新しいパスに更新されています。
-
型名のリネーム:
tabwriter.go
内で定義されていたTabWriter
という構造体名がWriter
に変更されました。- これに伴い、
TabWriter
を参照していたすべてのメソッド(例:(*TabWriter) AddLine()
->(*Writer) AddLine()
)や、コンストラクタ関数(例:New(writer io.Write, ...) *TabWriter
->New(writer io.Write, ...) *Writer
)のシグネチャも変更されています。 - この変更は、Goの慣習である
io.Writer
インターフェースを実装する型にはWriter
という名前を付けるという原則に則ったものです。これにより、tabwriter
パッケージのWriter
型がio.Writer
インターフェースと互換性があることが、名前から直感的に理解できるようになります。
-
Makefile
の更新:src/lib/tabwriter/Makefile
という新しいMakefile
が追加されました。このMakefile
は、tabwriter
パッケージのビルド、テスト、クリーンアップ、インストールに関するルールを定義しています。src/lib/clean.bash
とsrc/lib/make.bash
も更新され、Goのビルドシステムがtabwriter
パッケージを認識し、適切に処理するように変更されています。特にclean.bash
では、クリーンアップ対象のパッケージリストにtabwriter
が追加され、make.bash
ではビルド対象のディレクトリにtabwriter
が追加されています。
-
テストの追加:
src/lib/tabwriter/tabwriter_test.go
という新しいテストファイルが追加されました。これは、tabwriter
パッケージの基本的な機能が正しく動作することを検証するためのものです。Check
関数は、与えられた入力文字列がtabwriter
によって期待される出力文字列に変換されるかを検証します。Test1
関数は、いくつかの基本的なテストケース(空行、単純な文字列、タブ区切りの文字列)を実行し、tabwriter
の動作を確認します。- テストには、
Buffer
というカスタムのio.Writer
実装が使用されており、tabwriter.Writer
の出力をキャプチャして検証できるようにしています。
これらの変更により、tabwriter
はGo言語の標準ライブラリとして、より堅牢で、Goの設計思想に沿った形で提供されることになりました。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主に以下のファイルに集中しています。
-
usr/gri/pretty/tabwriter.go
からsrc/lib/tabwriter/tabwriter.go
への移動と内容変更:- ファイル自体が移動し、パッケージ名が
main
からtabwriter
に変更されました。 - 最も重要な変更は、構造体
TabWriter
の名前がWriter
に変更された点です。--- a/usr/gri/pretty/tabwriter.go +++ b/src/lib/tabwriter/tabwriter.go @@ -61,12 +61,12 @@ func (b *ByteArray) Append(s *[]byte) { // ---------------------------------------------------------------------------- -// Tabwriter is a filter implementing the IO.Write interface. It assumes +// Writer is a filter implementing the io.Write interface. It assumes // that the incoming bytes represent ASCII encoded text consisting of // lines of tab-separated "cells". Cells in adjacent lines constitute -// a column. Tabwriter rewrites the incoming text such that all cells -// in a column have the same width; thus it effectively aligns cells. -// It does this by adding padding where necessary. +// a column. Writer rewrites the incoming text such that all cells in +// a column have the same width; thus it effectively aligns cells. It +// does this by adding padding where necessary. // // Formatting can be controlled via parameters: // @@ -84,7 +84,7 @@ func (b *ByteArray) Append(s *[]byte) { // pendant cell and tab width. -export type TabWriter struct { +export type Writer struct { // TODO should not export any of the fields // configuration writer io.Write; @@ -100,12 +100,12 @@ export type TabWriter struct { } -func (b *TabWriter) AddLine() { +func (b *Writer) AddLine() { b.lines.Push(array.NewIntArray(0)); } -func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter { +func (b *Writer) Init(writer io.Write, tabwidth, padding int, usetabs bool) *Writer { b.writer = writer; b.tabwidth = tabwidth; b.padding = padding; @@ -120,18 +120,18 @@ func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) * } -func (b *TabWriter) Line(i int) *array.IntArray { +func (b *Writer) Line(i int) *array.IntArray { return b.lines.At(i).(*array.IntArray); } -func (b *TabWriter) LastLine() *array.IntArray { +func (b *Writer) LastLine() *array.IntArray { return b.lines.At(b.lines.Len() - 1).(*array.IntArray); } // debugging support -func (b *TabWriter) Dump() { +func (b *Writer) Dump() { pos := 0; for i := 0; i < b.lines.Len(); i++ { line := b.Line(i); @@ -147,7 +147,7 @@ func (b *TabWriter) Dump() { } -func (b *TabWriter) Write0(buf *[]byte) *os.Error { +func (b *Writer) Write0(buf *[]byte) *os.Error { n, err := b.writer.Write(buf); if n != len(buf) && err == nil { err = os.EIO; @@ -161,7 +161,7 @@ var Blanks = &[]byte{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '} var Newline = &[]byte{'\n'} -func (b *TabWriter) WritePadding(textw, cellw int) (err *os.Error) { +func (b *Writer) WritePadding(textw, cellw int) (err *os.Error) { if b.usetabs { // make cell width a multiple of tabwidth cellw = ((cellw + b.tabwidth - 1) / b.tabwidth) * b.tabwidth; @@ -192,7 +192,7 @@ exit: } -func (b *TabWriter) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error) { +func (b *Writer) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error) { pos = pos0; for i := line0; i < line1; i++ { line := b.Line(i); @@ -233,7 +233,7 @@ func utflen(buf *[]byte) int { } -func (b *TabWriter) Format(pos0 int, line0, line1 int) (pos int, err *os.Error) { +func (b *Writer) Format(pos0 int, line0, line1 int) (pos int, err *os.Error) { pos = pos0; column := b.widths.Len(); last := line0; @@ -284,13 +284,13 @@ exit: } -func (b *TabWriter) Append(buf *[]byte) { +func (b *Writer) Append(buf *[]byte) { b.buf.Append(buf); b.width += len(buf); } -/* export */ func (b *TabWriter) Flush() *os.Error { +/* export */ func (b *Writer) Flush() *os.Error { dummy, err := b.Format(0, 0, b.lines.Len()); // reset (even in the presence of errors) b.buf.Clear(); @@ -301,7 +301,7 @@ func (b *TabWriter) Append(buf *[]byte) { } -/* export */ func (b *TabWriter) Write(buf *[]byte) (written int, err *os.Error) { +/* export */ func (b *Writer) Write(buf *[]byte) (written int, err *os.Error) { i0, n := 0, len(buf); // split text into cells @@ -319,7 +319,7 @@ func (b *TabWriter) Append(buf *[]byte) { // The last line has only one cell which does not have an // impact on the formatting of the following lines (the // last cell per line is ignored by Format), thus we can - // flush the TabWriter contents. + // flush the Writer contents. err = b.Flush(); if err != nil { return i0, err; @@ -338,6 +338,6 @@ func (b *TabWriter) Append(buf *[]byte) { } -export func New(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter { - return new(TabWriter).Init(writer, tabwidth, padding, usetabs) +export func New(writer io.Write, tabwidth, padding int, usetabs bool) *Writer { + return new(Writer).Init(writer, tabwidth, padding, usetabs) }
- ファイル自体が移動し、パッケージ名が
-
src/lib/tabwriter/Makefile
の新規追加:tabwriter
パッケージをビルドするためのMakefile
が新しく作成されました。これには、コンパイル、テスト、クリーンアップ、インストールなどのターゲットが含まれています。
-
src/lib/tabwriter/tabwriter_test.go
の新規追加:tabwriter
パッケージの機能テストを行うためのGoテストファイルが追加されました。
コアとなるコードの解説
tabwriter.go
の変更
- 型名のリネーム:
TabWriter
からWriter
への変更は、Go言語のインターフェース設計における重要な慣習を反映しています。io.Writer
インターフェースは、Write([]byte) (n int, err error)
メソッドを持つ任意の型が実装できます。tabwriter
パッケージのWriter
型がこのインターフェースを実装することで、tabwriter.Writer
のインスタンスをio.Writer
を期待するあらゆる関数に渡すことができるようになります。これにより、tabwriter
はGoのI/Oエコシステムにシームレスに統合され、柔軟な利用が可能になります。例えば、fmt.Fprintf
のような関数にtabwriter.Writer
を渡して、整形された出力を直接生成することができます。 - コメントの更新: 型名のリネームに伴い、関連するコメントも
Tabwriter
からWriter
に更新され、コードの整合性が保たれています。
tabwriter_test.go
の追加
- テストフレームワークの利用: Go言語の標準テストパッケージ
testing
が使用されています。*testing.T
はテスト関数に渡され、テストの失敗を報告したり、ログを出力したりするために使用されます。 - カスタム
Buffer
の実装:tabwriter.Writer
はio.Writer
インターフェースを実装する任意の型に出力できます。テストでは、Buffer
というカスタム型がio.Writer
インターフェースを実装し、tabwriter.Writer
からの出力をメモリ上にキャプチャします。これにより、生成された出力を期待される文字列と比較して、tabwriter
の動作を検証することができます。 Check
関数の導入:Check
関数は、テストケースの共通ロジックをカプセル化しています。これにより、各テストケースは簡潔に記述でき、可読性が向上します。- 基本的なテストケース:
Test1
関数では、空行、単純な文字列、タブ区切りの文字列など、tabwriter
の基本的な動作を検証するテストケースが用意されています。これにより、tabwriter
が期待通りにテキストを整形し、列を揃えることができるかを確認します。
これらの変更は、tabwriter
がGo言語の標準ライブラリとして成熟し、より使いやすく、信頼性の高いものになるための重要なステップでした。
関連リンク
- Go言語の
io
パッケージ: https://pkg.go.dev/io - Go言語の
text/tabwriter
パッケージ: https://pkg.go.dev/text/tabwriter (現在のGoの標準ライブラリにおけるtabwriter
のドキュメント)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ
- Go言語の初期開発に関する情報(必要に応じてWeb検索)