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

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

このコミットは、usr/gri/pretty ディレクトリ内の3つのGo言語ファイル、printer.gotabwriter.gountab.go に変更を加えています。主な変更は、tabwriter.go における大幅な修正と機能強化であり、エラーハンドリングの改善、ドキュメンテーションの追加、および新しいオプションの導入が含まれています。

コミット

commit 0998eaf4a197cbdba2171fb6ccddf2830a31b110
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Nov 20 16:26:43 2008 -0800

    - correct error handling throughout
    - documentation, cleanups
    - more options
    
    R=r
    OCL=19736
    CL=19736

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

https://github.com/golang/go/commit/0998eaf4a197cbdba2171fb6ccddf2830a31b110

元コミット内容

- correct error handling throughout
- documentation, cleanups
- more options

変更の背景

このコミットは、Go言語の初期段階におけるprettyパッケージ(現在のtext/tabwriterパッケージの前身)の改善を目的としています。主な背景としては、以下の点が挙げられます。

  1. エラーハンドリングの強化: 以前の実装ではエラー処理が不十分であったため、堅牢性を高めるために全体的なエラーハンドリングの見直しと修正が行われました。特に、io.Writerインターフェースを介した書き込み操作において、エラーが発生した場合に適切に伝播させる必要がありました。
  2. ドキュメンテーションの充実: コードの可読性と保守性を向上させるため、TabWriterの機能、動作原理、および設定オプションに関する詳細なコメントが追加されました。これにより、他の開発者がこのパッケージを理解し、利用しやすくなります。
  3. 機能オプションの追加: TabWriterの柔軟性を高めるために、新しい設定オプションが導入されました。具体的には、パディングの量を制御するpaddingオプションが追加され、より細かなフォーマット調整が可能になりました。
  4. コードのクリーンアップとリファクタリング: ByteArray構造体から不要なメソッドが削除され、TabWriterの初期化関数がMakeTabWriterからNewに変更されるなど、全体的なコードの整理とリファクタリングが行われました。これにより、コードベースがより簡潔で効率的になりました。
  5. Go言語の標準ライブラリへの統合を見据えて: このprettyパッケージは、後にGoの標準ライブラリであるtext/tabwriterとして統合されることになります。このコミットは、その標準ライブラリとしての品質と機能性を高めるための重要なステップであったと考えられます。特に、弾性タブストップの概念をGoのツールチェーンに組み込む上で、その基盤となるtabwriterの実装を洗練させる必要がありました。

前提知識の解説

1. text/tabwriterパッケージと「弾性タブストップ (Elastic Tabstops)」

Go言語の標準ライブラリであるtext/tabwriterパッケージは、テキストを整形し、列を揃えるための強力なツールです。このパッケージは、Nick Gravgaardによって提唱された「弾性タブストップ (Elastic Tabstops)」というアルゴリズムを実装しています。

  • 弾性タブストップとは: 従来の固定幅のタブストップとは異なり、弾性タブストップは、各列の幅をその列内の最長の要素に合わせて動的に調整します。これにより、異なる長さのテキストが混在していても、常にきれいに列が揃った出力が得られます。例えば、以下のようなテキストがあった場合:

    Name    Value    Description
    Short   1        A short description
    VeryLongName 10000 A very long description that spans multiple words
    

    弾性タブストップを適用すると、各列の幅が自動的に調整され、以下のように整形されます。

    Name         Value    Description
    Short        1        A short description
    VeryLongName 10000    A very long description that spans multiple words
    

    これは、コードの整形(特に変数宣言や構造体のフィールドなど)や、CLIツールの出力整形において非常に有用です。

  • tabwriterの動作原理: tabwriterio.Writerインターフェースを実装しており、入力されたバイトストリームを処理し、タブ (\t) や改行 (\n) を区切り文字として認識します。そして、内部的に各セルの幅を計算し、必要に応じて空白やタブを挿入して列を揃えた後、最終的な整形済みテキストを出力ライターに書き込みます。

2. io.Writerインターフェース

Go言語におけるio.Writerインターフェースは、バイトのスライスを書き込むための基本的なインターフェースです。

type Writer interface {
    Write(p []byte) (n int, err error)
}

Writeメソッドは、pバイトをデータストリームに書き込み、書き込まれたバイト数nと、書き込み中に発生したエラーerrを返します。このインターフェースは、ファイル、ネットワーク接続、標準出力など、様々な出力先に対して統一された書き込み操作を提供します。tabwriterio.Writerを実装しているということは、tabwriterのインスタンスを、io.Writerを引数にとるあらゆる関数に渡すことができることを意味します。

3. gofmtとの関連

gofmtは、Go言語の公式なコード整形ツールです。gofmtは、Goのコードを標準的なスタイルに自動的に整形するためにtext/tabwriterパッケージを内部的に利用しています。これにより、Goのコードベース全体で一貫したフォーマットが保証され、可読性が向上します。このコミットで行われたtabwriterの改善は、gofmtの整形品質にも直接的に影響を与えるものでした。

技術的詳細

このコミットは、usr/gri/prettyパッケージのprinter.gotabwriter.gountab.goの3つのファイルにわたる変更を含んでいます。

usr/gri/pretty/printer.go

  • TabWriterの初期化変更:
    • 変更前: P.writer = TabWriter.MakeTabWriter(OS.Stdout, usetabs.BVal(), int(tabwidth.IVal()));
    • 変更後: P.writer = TabWriter.New(OS.Stdout, int(tabwidth.IVal()), 1, usetabs.BVal());
    • TabWriterのコンストラクタがMakeTabWriterからNewに変更され、引数の順序と内容が更新されました。特に、新しいpadding引数(ここでは1が渡されている)が追加されています。

usr/gri/pretty/tabwriter.go

このファイルが最も広範な変更を受けています。

  • ByteArrayの変更:
    • ByteArray構造体からLen(), At(), Set()メソッドが削除されました。これは、ByteArrayがより汎用的なライブラリに移行されることを示唆しており、tabwriter内部でのByteArrayの利用方法が変更されたことを意味します。
    • Sliceメソッドのコメントが// BUG should really be &b.a[i : j]から// BUG should really be &b.a[i : j]に変更されていますが、これはコメントの修正であり、機能的な変更ではありません。
  • TabWriter構造体の変更:
    • TabWriter構造体にpadding intフィールドが追加されました。これにより、セル間の追加パディング量を設定できるようになります。
    • // TODO should not export any of the fieldsというコメントが追加され、内部フィールドのエクスポートに関する将来的な変更の意図が示されています。
  • Initメソッドのシグネチャ変更:
    • 変更前: func (b *TabWriter) Init(writer io.Write, usetabs bool, tabwidth int)
    • 変更後: func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter
    • padding引数が追加され、引数の順序が変更されました。また、メソッドが*TabWriterを返すようになり、メソッドチェーンを可能にしています。
  • エラーハンドリングの導入と改善:
    • Write0という新しいヘルパー関数が導入され、io.Writerへの書き込みエラーをより適切に処理するようになりました。
    • PaddingメソッドがWritePaddingにリネームされ、err *os.Errorを返すようになりました。これにより、パディング書き込み時のエラーが呼び出し元に伝播されるようになります。
    • PrintLinesメソッドがWriteLinesにリネームされ、pos int, err *os.Errorを返すようになりました。これにより、行の書き込みエラーが処理されるようになります。
    • Formatメソッドもpos int, err *os.Errorを返すように変更され、再帰的なフォーマット処理中のエラーが適切に伝播されるようになりました。
    • goto exit;というラベル付きgoto文が導入され、エラー発生時にクリーンアップ処理をスキップして関数を終了するパターンが採用されています。これはGoの初期の慣習であり、現代のGoではあまり推奨されません。
  • Writeメソッドのロジック変更:
    • Writeメソッドの内部ロジックが大幅に変更されました。以前はタブと改行を個別に処理していましたが、新しい実装では、入力バッファをタブまたは改行で区切られた「セル」に分割し、各セルをb.Appendで内部バッファに追加し、b.LastLine().Push(b.width)でセルの幅を記録するようになりました。
    • 改行 (\n) が検出された場合、現在の行が1つのセルしか持たない(つまり、その行がフォーマットに影響を与えない)場合にのみFlush()を呼び出すようになりました。これにより、部分的なフラッシュが可能になり、パフォーマンスが向上する可能性があります。
  • Tab()Newline()メソッドの削除:
    • 以前はTab()Newline()Writeメソッドから直接呼び出されていましたが、新しいWriteメソッドのロジックではこれらのメソッドは不要となり、削除されました。
  • Flush()メソッドの導入:
    • Flush()メソッドが導入され、内部バッファの内容を強制的に出力ライターに書き出し、TabWriterの状態をリセットする機能が提供されました。
  • Newコンストラクタ関数の導入:
    • MakeTabWriterの代わりに、Newという新しいコンストラクタ関数が導入されました。これは、new(TabWriter).Init(...)という形式でTabWriterのインスタンスを初期化し、返すための慣用的な方法です。
  • utflen関数の追加:
    • // TODO use utflen for correct formattingというコメントとともにutflen関数が追加されました。これは、UTF-8文字列の表示幅を正しく計算するためのプレースホルダー関数であり、将来的なUTF-8サポートの意図を示しています。

usr/gri/pretty/untab.go

  • TabWriterの初期化変更:
    • printer.goと同様に、tabwriter.MakeTabWriterの呼び出しがtabwriter.Newに変更され、新しいpadding引数(ここでも1)が追加されています。

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

このコミットのコアとなる変更は、usr/gri/pretty/tabwriter.goファイルに集中しています。特に以下の部分が重要です。

  1. TabWriter構造体へのpaddingフィールドの追加:
    --- a/usr/gri/pretty/tabwriter.go
    +++ b/usr/gri/pretty/tabwriter.go
    @@ -76,27 +61,42 @@ func (b *ByteArray) Append(s *[]byte) {
     
     
     // ----------------------------------------------------------------------------
    -// Implementation of flexible tab stops.
    -
    -// TabWriter is a representation for a list of lines consisting of
    -// cells. A new cell is added for each Tab() call, and a new line
    -// is added for each Newline() call.\n//\n// The lines are formatted and printed such that all cells in a column\n-// of adjacent cells have the same width (by adding padding). For more\n-// details see: http://nickgravgaard.com/elastictabstops/index.html .\n+// Tabwriter is a filter implementing the IO.Write interface. It assumes\n+// that the incoming bytes represent ASCII encoded text consisting of\n+// lines of tab-separated "cells". Cells in adjacent lines constitute\n+// a column. Tabwriter rewrites the incoming text such that all cells\n+// in a column have the same width; thus it effectively aligns cells.\n+// It does this by adding padding where necessary.\n+//\n+// Formatting can be controlled via parameters:\n+//\n+// tabwidth  the minimal with of a cell\n+// padding   additional padding\n+// usetabs   use tabs instead of blanks for padding\n+//           (for correct-looking results, tabwidth must correspond\n+//           to the tabwidth in the editor used to look at the result)\n //\n    -// The lines are formatted and printed such that all cells in a column\n    -// of adjacent cells have the same width (by adding padding). For more\n    -// details see: http://nickgravgaard.com/elastictabstops/index.html .\n+// (See alse http://nickgravgaard.com/elastictabstops/index.html)\n+\n+// TODO Should support UTF-8\n+// TODO Should probably implement a couple of trivial customization options\n+//      such as arbitrary padding character, left/right alignment, and inde-\n+//      pendant cell and tab width.\n+\n     export type TabWriter struct {\n    +\t// TODO should not export any of the fields\n     \t// configuration\n     \twriter io.Write;\n    -\tusetabs bool;\n     \ttabwidth int;\n    +\tpadding int;\n    +\tusetabs bool;\n     
     \t// current state
     \tbuf ByteArray;  // the collected text w/o tabs and newlines
     \twidth int;  // width of last incomplete cell
     \tlines array.Array;  // list of lines; each line is a list of cell widths
    -\twidths array.IntArray;  // list of column widths - (re-)used during formatting
    +\twidths array.IntArray;  // list of column widths - re-used during formatting
     }
    
  2. Initメソッドのシグネチャ変更とNewコンストラクタの導入:
    --- a/usr/gri/pretty/tabwriter.go
    +++ b/usr/gri/pretty/tabwriter.go
    @@ -105,15 +105,18 @@ func (b *TabWriter) AddLine() {
     }
     
     
    -func (b *TabWriter) Init(writer io.Write, usetabs bool, tabwidth int) {
    +func (b *TabWriter) Init(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter {
     \tb.writer = writer;
    -\tb.usetabs = usetabs;\n     \tb.tabwidth = tabwidth;\n    +\tb.padding = padding;\n    +\tb.usetabs = usetabs;\n     \t
     \tb.buf.Init(1024);\n     \tb.lines.Init(0);\n     \tb.widths.Init(0);\n     \tb.AddLine();  // the very first line
    +\t\n+\treturn b;\n     }
     
     
    -export func MakeTabWriter(writer io.Write, usetabs bool, tabwidth int) *TabWriter {
    -\tb := new(TabWriter);\n-\tb.Init(writer, usetabs, tabwidth);\n-\treturn b;\n    +export func New(writer io.Write, tabwidth, padding int, usetabs bool) *TabWriter {
    +\treturn new(TabWriter).Init(writer, tabwidth, padding, usetabs)
     }
    
  3. Writeメソッドのロジック変更とエラーハンドリングの導入:
    --- a/usr/gri/pretty/tabwriter.go
    +++ b/usr/gri/pretty/tabwriter.go
    @@ -226,85 +267,77 @@ func (b *TabWriter) Format(pos int, line0, line1 int) int {
     }
     
     
    -func (b *TabWriter) EmptyLine() bool {
    -\treturn b.LastLine().Len() == 0 && b.width == 0;\n     }
     
     
    -func (b *TabWriter) Tab() {
    -\tb.LastLine().Push(b.width);\n     \tb.width = 0;\n    -}
    -
    -
    -func (b *TabWriter) Newline() {
    -\tb.Tab();  // add last cell to current line
    -\n-\tif b.LastLine().Len() == 1 {
    -\t\t// The current line has only one cell which does not have an impact
    -\t\t// on the formatting of the following lines (the last cell per line
    -\t\t// is ignored by Format), thus we can print the TabWriter contents.\n-\t\tif b.widths.Len() != 0 {
    -\t\t\tpanic("internal error");\n-\t\t}\n-\t\tb.Format(0, 0, b.lines.Len());\n-\t\tif b.widths.Len() != 0 {
    -\t\t\tpanic("internal error");\n-\t\t}\n-\n-\t\t// reset TabWriter
    -\t\tb.width = 0;\n-\t\tb.buf.Clear();\n-\t\tb.lines.Init(0);\n-\t}\n-\n     \tb.AddLine();
    +\t/* export */ func (b *TabWriter) Flush() *os.Error {
    +\t\tdummy, err := b.Format(0, 0, b.lines.Len());
    +\t\t// reset (even in the presence of errors)
    +\t\tb.buf.Clear();
    +\t\tb.width = 0;
    +\t\tb.lines.Init(0);
    +\t\tb.AddLine();
    +\t\treturn err;
    +\t}
     }
     
     
    -func (b *TabWriter) Write(buf *[]byte) (i int, err *os.Error) {
    +\t/* export */ func (b *TabWriter) Write(buf *[]byte) (written int, err *os.Error) {
     \ti0, n := 0, len(buf);
    -\tfor i = 0; i < n; i++ {
    -\t\tswitch buf[i] {
    -\t\tcase '\t':
    -\t\t\tb.width += i - i0;
    -\t\t\tb.buf.Append(buf[i0 : i]);
    -\t\t\ti0 = i + 1;  // don't append '\t'
    -\t\t\tb.Tab();
    -\t\tcase '\n':
    -\t\t\tb.width += i - i0;
    -\t\t\tb.buf.Append(buf[i0 : i]);
    -\t\t\ti0 = i + 1;  // don't append '\n'
    -\t\t\tb.Newline();
    +\t\n+\t// split text into cells
    +\tfor i := 0; i < n; i++ {
    +\t\tif ch := buf[i]; ch == '\t' || ch == '\n' {
    +\t\t\tb.Append(buf[i0 : i]);
    +\t\t\ti0 = i + 1;  // exclude ch from (next) cell
    +\n+\t\t\t// terminate cell
    +\t\t\tb.LastLine().Push(b.width);
    +\t\t\tb.width = 0;
    +\n+\t\t\tif ch == '\n' {
    +\t\t\t\tif b.LastLine().Len() == 1 {
    +\t\t\t\t\t// The last line has only one cell which does not have an
    +\t\t\t\t\t// impact on the formatting of the following lines (the
    +\t\t\t\t\t// last cell per line is ignored by Format), thus we can
    +\t\t\t\t\t// flush the TabWriter contents.
    +\t\t\t\t\terr = b.Flush();
    +\t\t\t\t\tif err != nil {
    +\t\t\t\t\t\treturn i0, err;
    +\t\t\t\t\t}
    +\t\t\t\t} else {
    +\t\t\t\t\t// We can't flush yet - just add a new line.
    +\t\t\t\t\tb.AddLine();
    +\t\t\t\t}
    +\t\t\t}
     \t\t}
     \t}
    -\tb.width += n - i0;\n-\tb.buf.Append(buf[i0 : n]);
    -\treturn i, nil;\n+\t\n+\t// append leftover text
    +\tb.Append(buf[i0 : n]);
    +\treturn n, nil;
     }
    

コアとなるコードの解説

1. TabWriter構造体へのpaddingフィールドの追加

paddingフィールドの追加は、TabWriterの整形能力を向上させる重要な変更です。これにより、各セルの内容と次のセルの開始位置との間に、ユーザーが指定した追加の空白(またはタブ)を挿入できるようになります。これは、出力の視覚的な間隔を調整し、より読みやすいレイアウトを作成するために使用されます。例えば、コードの整形において、変数名と型の間に追加のスペースを入れることで、視覚的な区切りを明確にすることができます。

2. Initメソッドのシグネチャ変更とNewコンストラクタの導入

  • Initメソッドの変更: Initメソッドのシグネチャが変更され、padding引数が追加されたことで、TabWriterの初期化時にパディングの値を設定できるようになりました。また、*TabWriterを返すようになったことで、new(TabWriter).Init(...)のようなチェーン呼び出しが可能になり、より簡潔な初期化コードが書けるようになりました。
  • Newコンストラクタの導入: MakeTabWriterからNewへの変更は、Go言語におけるコンストラクタ関数の命名規則に合わせたものです。Newという名前は、新しいインスタンスを作成して返すことを明確に示します。これは、Goの標準ライブラリで広く採用されているパターンであり、コードの一貫性と可読性を高めます。

3. Writeメソッドのロジック変更とエラーハンドリングの導入

この変更は、TabWriterの内部動作と堅牢性に大きな影響を与えます。

  • ロジックの変更: 以前のWriteメソッドは、タブと改行を個別のイベントとして処理し、Tab()Newline()といった内部メソッドを呼び出していました。新しいロジックでは、入力バイトストリームを直接解析し、タブや改行をセルの区切りとして扱います。これにより、より効率的で直接的なテキスト処理が可能になります。特に、b.Append(buf[i0 : i])でセルの内容を内部バッファに追加し、b.LastLine().Push(b.width)でそのセルの幅を記録するアプローチは、弾性タブストップの計算に必要な情報を効率的に収集します。
  • エラーハンドリングの強化: 以前のバージョンでは、io.Writerへの書き込みエラーが適切に処理されていませんでした。新しい実装では、Write0WritePaddingWriteLinesといったヘルパー関数が導入され、これらの関数が*os.Errorを返すようになりました。これにより、下位レベルの書き込み操作で発生したエラーが、Writeメソッド、さらにはFlushメソッドを通じて呼び出し元に正確に伝播されるようになります。goto exit;の使用は、エラー発生時に複数のクリーンアップステップをスキップして関数を終了させるためのGo初期のイディオムです。
  • 部分的なフラッシュ: Writeメソッド内で改行が検出された際に、b.LastLine().Len() == 1(つまり、現在の行が1つのセルしか持たない)という条件でb.Flush()を呼び出すロジックは、パフォーマンス最適化の一環です。これは、その行が後続のフォーマットに影響を与えないため、早期にバッファをフラッシュしてリソースを解放できることを意味します。これにより、特に大きな入力ストリームを処理する際に、メモリ使用量を抑え、処理の応答性を向上させることができます。

これらの変更は、TabWriterがより堅牢で、柔軟性があり、効率的なテキスト整形ツールとなるための基盤を築きました。特に、エラーハンドリングの改善は、このパッケージがGoの標準ライブラリとして採用される上で不可欠な品質向上でした。

関連リンク

参考にした情報源リンク