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

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

このコミットは、Go言語の初期のsrc/lib/testing.goファイルに対する変更です。testing.goは、Go言語のテストフレームワークの基盤となるファイルであり、テストの実行、エラーの記録、ログの出力など、テストに関する基本的な機能を提供します。このファイルは、Goプログラムの品質と信頼性を保証するための重要な役割を担っています。

コミット

このコミットは、Go言語のテスト出力において、改行の後に自動的にタブを追加する機能Tabifyを導入し、LogおよびLogfメソッドでそのTabify関数を使用するように変更しています。これにより、テスト出力の可読性が向上し、特に複数行にわたるログメッセージが整形されて表示されるようになります。

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

https://github.com/golang/go/commit/0444d697c14907b42b4369459f25785c1c946e97

元コミット内容

automatically add tabs after newlines

R=gri
DELTA=12  (10 added, 0 deleted, 2 changed)
OCL=19758
CL=19758

変更の背景

この変更が行われた2008年11月は、Go言語がまだ一般に公開される前の非常に初期の段階でした。当時のGoのテストフレームワークは現在とは異なり、testing.goのような基本的なライブラリが構築されている最中でした。

テストのログ出力は、開発者がテストの実行中に何が起こっているかを理解するために非常に重要です。しかし、複数行にわたるログメッセージが整形されずにそのまま出力されると、その可読性は著しく低下します。特に、エラーメッセージやデバッグ情報が長くなる場合、どの行がどのログメッセージに属するのかが分かりにくくなります。

このコミットの目的は、LogLogfといったログ出力関数が生成するメッセージにおいて、改行の後に自動的にタブ(\t)を挿入することで、出力の階層構造を視覚的に明確にし、可読性を向上させることにありました。これにより、テスト結果の分析が容易になり、開発効率の向上に寄与します。

前提知識の解説

Go言語の初期のtestingパッケージ

Go言語のtestingパッケージは、ユニットテスト、ベンチマークテスト、例(Example)テストなどを記述するための標準ライブラリです。このコミットが作成された時点では、現在のtestingパッケージの機能はまだ限定的であり、src/lib/testing.goがその中心的な役割を担っていました。

  • type T struct: テスト関数に渡される構造体で、テストの失敗を記録したり、ログを出力したりするためのメソッドを提供します。
  • func (t *T) Log(args ...): テスト中に情報をログとして出力するためのメソッドです。引数は可変長で、fmt.sprintlnによって文字列に変換されます。
  • func (t *T) Logf(format string, args ...): Logと同様にログを出力しますが、fmt.sprintfを使用してフォーマット文字列に基づいて整形されたメッセージを出力します。

fmt.sprintlnfmt.sprintf

Go言語のfmtパッケージは、フォーマットされたI/Oを実装するためのパッケージです。

  • fmt.sprintln(args ...interface{}) string: 引数をスペースで区切り、最後に改行を追加して文字列を生成します。
  • fmt.sprintf(format string, args ...interface{}) string: フォーマット文字列と引数に基づいて整形された文字列を生成します。C言語のsprintfに似ています。

これらの関数は、テストのログメッセージを生成する際に使用され、このコミットではその出力に対してTabify関数が適用されるようになります。

Tabify関数の目的

Tabify関数は、入力文字列中の改行文字(\n)の直後にタブ文字(\t)を挿入することを目的としています。これにより、複数行にわたるテキストがインデントされ、視覚的に構造化された出力が得られます。例えば、以下のような文字列があったとします。

Line 1
Line 2
Line 3

Tabify関数を適用すると、以下のように変換されます。

Line 1
    Line 2
    Line 3

(ここではタブをスペース4つで表現しています)

これは、特にエラーメッセージのスタックトレースや、構造化されたデバッグ情報を出力する際に、その可読性を大幅に向上させます。

技術的詳細

このコミットの主要な変更点は、Tabify関数の導入とそのLogおよびLogfメソッドへの適用です。

Tabify関数の実装

// Insert tabs after newlines - but not the last one
func Tabify(s string) string {
	for i := 0; i < len(s) - 1; i++ {	// -1 because if last char is newline, don't bother
		if s[i] == '\n' {
			return s[0:i+1] + "\t" + Tabify(s[i+1:len(s)]);
		}
	}
	return s
}

Tabify関数は、文字列sを受け取り、再帰的に処理を行います。

  1. ループ処理: 文字列sの各文字を先頭から走査します。ループの条件がi < len(s) - 1となっているのは、文字列の最後の文字が改行であっても、その後にタブを挿入する必要がないためです。最後の改行の後にタブを挿入すると、不必要なインデントが追加される可能性があります。
  2. 改行文字の検出: s[i] == '\n'で改行文字を検出します。
  3. 再帰的なタブ挿入: 改行文字が見つかった場合、以下の処理を行います。
    • s[0:i+1]: 文字列の先頭から改行文字を含む部分文字列を抽出します。
    • "\t": 抽出した部分文字列の直後にタブ文字を挿入します。
    • Tabify(s[i+1:len(s)]): 改行文字の次の文字から文字列の最後までを新たな引数としてTabify関数を再帰的に呼び出します。これにより、文字列中に複数の改行が存在する場合でも、すべての改行の後にタブが挿入されます。
  4. 改行が見つからない場合: ループが終了しても改行文字が見つからなかった場合、元の文字列sをそのまま返します。これは、文字列中に改行が含まれていない場合や、再帰呼び出しの最終段階で残りの部分文字列に改行がない場合に発生します。

この再帰的なアプローチにより、文字列全体にわたって効率的にタブ挿入が行われます。

LogおよびLogfメソッドへの適用

Tabify関数が定義された後、testing.go内のLogおよびLogfメソッドが変更され、生成されたログメッセージにTabify関数を適用するようになりました。

変更前:

func (t *T) Log(args ...) {
	t.errors += "\t" + fmt.sprintln(args);
}

func (t *T) Logf(format string, args ...) {
	t.errors += fmt.sprintf("\t" + format, args);
	l := len(t.errors);
	if l > 0 && t.errors[l-1] != '\n' {
		t.errors += "\n"
	}
}

変更後:

func (t *T) Log(args ...) {
	t.errors += "\t" + Tabify(fmt.sprintln(args));
}

func (t *T) Logf(format string, args ...) {
	t.errors += Tabify(fmt.sprintf("\t" + format, args));
	l := len(t.errors);
	if l > 0 && t.errors[l-1] != '\n' {
		t.errors += "\n"
	}
}

この変更により、fmt.sprintlnfmt.sprintfによって生成されたログメッセージがt.errorsに追加される前に、Tabify関数によって整形されるようになりました。これにより、テストのログ出力が自動的にインデントされ、視覚的に整理された形式で表示されるようになります。

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

src/lib/testing.go

--- a/src/lib/testing.go
+++ b/src/lib/testing.go
@@ -14,6 +14,16 @@ func init() {
 	flag.Bool("chatty", false, &chatty, "chatty");
 }
 
+// Insert tabs after newlines - but not the last one
+func Tabify(s string) string {
+	for i := 0; i < len(s) - 1; i++ {	// -1 because if last char is newline, don't bother
+		if s[i] == '\n' {
+			return s[0:i+1] + "\t" + Tabify(s[i+1:len(s)]);
+		}
+	}
+	return s
+}
+
 export type T struct {
 	errors	string;
 	failed	bool;
@@ -31,11 +41,11 @@ func (t *T) FailNow() {
 }
 
 func (t *T) Log(args ...) {
-	t.errors += "\t" + fmt.sprintln(args);
+	t.errors += "\t" + Tabify(fmt.sprintln(args));
 }
 
 func (t *T) Logf(format string, args ...) {
-	t.errors += fmt.sprintf("\t" + format, args);
+	t.errors += Tabify(fmt.sprintf("\t" + format, args));
 	l := len(t.errors);
 	if l > 0 && t.errors[l-1] != '\n' {
 		t.errors += "\n"

コアとなるコードの解説

Tabify関数の追加

Tabify関数は、src/lib/testing.goファイルに新しく追加された関数です。この関数は、文字列内の改行文字の直後にタブ文字を挿入する役割を担います。再帰的な実装により、文字列全体を効率的に処理し、複数行にわたるログメッセージのインデントを自動化します。

Logメソッドの変更

Logメソッドは、テスト中に情報をログとして出力するために使用されます。変更前は、fmt.sprintlnで生成された文字列の前に直接タブを追加していました。変更後は、fmt.sprintlnの出力に対してTabify関数を適用することで、複数行のログメッセージが適切にインデントされるようになりました。

Logfメソッドの変更

Logfメソッドは、フォーマット文字列を使用してログメッセージを出力します。Logメソッドと同様に、変更前はfmt.sprintfで生成された文字列の前に直接タブを追加していました。変更後は、fmt.sprintfの出力に対してTabify関数を適用することで、フォーマットされた複数行のログメッセージも適切にインデントされるようになりました。

これらの変更により、Go言語のテストフレームワークは、より読みやすく、構造化されたログ出力を提供できるようになり、開発者のデバッグ作業を支援します。

関連リンク

参考にした情報源リンク