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

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

このコミットは、Go言語の公式ドキュメントである effective_go.html と、その中で使用されているサンプルコード eff_bytesize.go に修正と改善を加えるものです。主な目的は、ドキュメントの記述をより正確にし、特にカスタム型における String() メソッドと fmt パッケージの相互作用に関する説明を明確にすることにあります。

コミット

  • コミットハッシュ: 4074795e151813f303d5500d255901c6a3a796ef
  • 作者: Rob Pike r@golang.org
  • コミット日時: 2012年3月25日 (日) 11:34:51 +1100
  • コミットメッセージ:
    effective_go: cleanups and fixes
    Also explain the situation with recursive String methods more accurately,
    and clean up the code now that the fmt package is more careful.
    
    R=golang-dev, minux.ma, bradfitz
    CC=golang-dev
    https://golang.org/cl/5907047
    

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

https://github.com/golang/go/commit/4074795e151813f303d5500d255901c6a3a796ef

元コミット内容

effective_go: cleanups and fixes
Also explain the situation with recursive String methods more accurately,
and clean up the code now that the fmt package is more careful.

R=golang-dev, minux.ma, bradfitz
CC=golang-dev
https://golang.org/cl/5907047

変更の背景

このコミットの背景には、Go言語の標準ライブラリである fmt パッケージの進化と、それによって可能になった String() メソッドの実装における簡素化があります。

effective_go は、Go言語のイディオムやベストプラクティスを解説する公式ドキュメントであり、Goプログラマーにとって非常に重要なリソースです。このドキュメントは、Go言語の設計思想や効率的なコーディングスタイルを理解するために広く参照されています。

以前の fmt パッケージの挙動では、カスタム型が String() メソッドを実装し、その String() メソッド内で fmt.Sprintf を呼び出す際に、無限再帰に陥る可能性がありました。特に、%s%v のような文字列フォーマット指定子を使用した場合、fmt.Sprintf は引数の型が fmt.Stringer インターフェースを実装しているかをチェックし、もし実装していればその String() メソッドを呼び出そうとします。これにより、String() メソッドが自身を再帰的に呼び出し、スタックオーバーフローを引き起こす可能性がありました。

この問題を回避するため、以前は ByteSize 型の String() メソッドのように、fmt.Sprintf に渡す前に明示的に float64 に型変換を行うなどの工夫が必要でした。しかし、fmt パッケージが「より注意深く (more careful)」なった、つまり内部的な挙動が改善されたことで、このような明示的な型変換が不要になり、より直感的で簡潔なコードが書けるようになりました。

このコミットは、この fmt パッケージの改善を反映し、effective_go ドキュメント内の説明とサンプルコードを更新することで、読者が最新かつ正確な情報に基づいたGoプログラミングを学べるようにすることを目的としています。また、range ループや new キーワード、ブランク識別子に関する説明も、より明確で正確なものに修正されています。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。

  1. effective_go ドキュメント: Go言語の公式ドキュメントの一つで、Go言語のイディオム、ベストプラクティス、設計原則について解説しています。Goプログラマーがより効果的でGoらしいコードを書くための指針を提供します。

  2. fmt パッケージと fmt.Stringer インターフェース: fmt パッケージは、Go言語におけるフォーマットされたI/O(入出力)を提供します。fmt.Printffmt.Sprintf などの関数が含まれます。 fmt.Stringer インターフェースは、String() string というシグネチャを持つメソッドを定義するインターフェースです。任意の型がこのインターフェースを実装すると、その型の値を fmt パッケージの関数(例: %s%v フォーマット指定子)で出力する際に、自動的に String() メソッドが呼び出され、その戻り値が文字列として使用されます。

  3. String() メソッドの再帰問題: カスタム型が fmt.Stringer インターフェースを実装し、その String() メソッド内で fmt.Sprintf を呼び出す場合、特定のフォーマット指定子(%s, %q, %v, %x, %X など)を使用すると、fmt.Sprintf が再びその型の String() メソッドを呼び出そうとし、無限再帰に陥る可能性があります。これは、fmt.Sprintf が引数の型を検査し、fmt.Stringer インターフェースを実装していればその String() メソッドを呼び出すという挙動によるものです。

  4. range キーワード: for ... range ループは、スライス、配列、文字列、マップ、チャネルを反復処理するために使用されます。

    • スライス/配列の場合: for index, value := range collection の形式で、インデックスと値を取得します。
    • マップの場合: for key, value := range map の形式で、キーと値を取得します。
    • キーやインデックスのみが必要な場合は for key := range map のように2番目の変数を省略できます。
    • 値のみが必要な場合は、ブランク識別子 _ を使用して for _, value := range collection のようにキーやインデックスを破棄できます。
  5. ブランク識別子 (_): Go言語のブランク識別子 _ は、変数を宣言したがその値を使用しない場合に、コンパイラのエラーを回避するために使用されます。例えば、関数の戻り値の一部を無視したい場合や、range ループでインデックスやキーを無視したい場合などに利用されます。

  6. new キーワード: new はGo言語の組み込み関数で、指定された型のゼロ値を持つ新しい項目をメモリに割り当て、そのアドレス(ポインタ)を返します。他の言語の new とは異なり、メモリを初期化するのではなく、ゼロ値で埋めます。例えば、new(int)*int 型のポインタを返し、その指す値は 0 に初期化されます。

技術的詳細

このコミットにおける技術的な変更点は多岐にわたりますが、特に重要なのは fmt パッケージの改善と、それに伴う String() メソッドの実装の簡素化、そして effective_go.html における説明の正確性の向上です。

  1. fmt パッケージの「より注意深い」挙動: コミットメッセージにある「fmt パッケージがより注意深くなった (the fmt package is more careful)」という記述は、fmt.Sprintf がカスタム型の String() メソッドを呼び出す際の内部ロジックが改善されたことを示唆しています。具体的には、fmt.Sprintffmt.Stringer インターフェースを実装した型を処理する際に、無限再帰を避けるためのより洗練されたチェックを行うようになったと考えられます。これにより、ByteSize 型の String() メソッドのように、以前は再帰を防ぐために必要だった float64() への明示的な型変換が不要になりました。%f のような数値フォーマット指定子を使用する場合、fmt.SprintfString() メソッドを呼び出すのではなく、数値として値をフォーマットするため、元々再帰の問題は発生しませんでしたが、この変更は fmt パッケージがより堅牢になったことを示しています。

  2. String() メソッドにおける fmt.Sprintf の安全な使用: effective_go.html の変更では、String() メソッド内で fmt.Sprintf を呼び出すこと自体は問題ないが、%s, %q, %v, %x, %X のような文字列フォーマット指定子を使って、ネストされた Sprintf 呼び出しを通じて String() メソッドが再帰的に呼び出されないように注意する必要がある、と明確に説明されています。ByteSize の例では %f を使用しているため安全であると強調されています。これは、fmt パッケージが改善された後でも、開発者が再帰の可能性を理解し、適切なフォーマット指定子を選択することの重要性を示しています。

  3. range ループの例の拡充と明確化: effective_go.html では、range ループの使用例が追加・修正されました。

    • マップのキーと値の両方を反復処理する例 (for key, value := range oldMap) が追加され、マップのコピー方法が示されました。
    • マップのキーのみを反復処理する例 (for key := range m) が追加され、キーのみが必要な場合の簡潔な記述方法が示されました。
    • 値のみを反復処理する例では、以前はマップの例 (for _, value := range m) であったものが、配列の例 (for _, value := range array) に変更され、ブランク識別子 _ の使用法がより一般的な文脈で説明されました。これにより、range がマップと配列/スライスで異なる挙動(マップはキーと値、配列/スライスはインデックスと値)を示すことがより明確になりました。
  4. new キーワードの説明の修正: new キーワードの説明において、「it only zeroes it.」が「it only zeros it.」に修正されました。これは単なるスペルミスまたは文法的な修正であり、意味的な変更はありませんが、ドキュメントの正確性を高めるための細かな改善です。

  5. その他の軽微な修正:

    • バイト配列の比較ルーチンのコメントがより詳細になりました。
    • スライシングの説明文がより自然な表現に修正されました。
    • 並列計算に関する説明で、「gc (6g, etc.)」が「Go runtime」に修正されました。これは、Goのコンパイラ(gc)ではなく、Goのランタイムが並列処理のスケジューリングを担当するという、より正確な表現です。
    • 「webserver」が「web server」に修正されるなど、全体的にドキュメントの表現が洗練されています。

これらの変更は、Go言語のドキュメントの品質を向上させ、開発者がより正確で最新の情報を得られるようにするための継続的な取り組みの一環です。

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

このコミットにおける主要なコード変更は、doc/effective_go.htmldoc/progs/eff_bytesize.go の2つのファイルにわたります。

doc/effective_go.html の変更

effective_go.html では、主に以下のセクションが変更されています。

  1. range ループに関する説明の追加と修正:

    --- a/doc/effective_go.html
    +++ b/doc/effective_go.html
    @@ -625,9 +625,28 @@ or reading from a channel, a <code>range</code> clause can
     manage the loop.
     </p>
     <pre>
    -var m map[string]int
    +for key, value := range oldMap {
    +    newMap[key] = value
    +}
    +</pre>
    +
    +<p>
    +If you only need the first item in the range (the key or index), drop the second:
    +</p>
    +<pre>
    +for key := range m {
    +    if expired(key) {
    +        delete(m, key)
    +    }\n
    +}\n
    +</pre>
    +\n
    +<p>
    +If you only need the second item in the range (the value), use the <em>blank identifier</em>, an underscore, to discard the first:\n
    +</p>\n
    +<pre>\n
     sum := 0
    -for _, value := range m {  // key is unused
    +for _, value := range array {
         sum += value
     }
     </pre>
    
  2. new キーワードの説明の修正:

    --- a/doc/effective_go.html
    +++ b/doc/effective_go.html
    @@ -1003,7 +1022,7 @@ but the rules are simple.\n Let\'s talk about <code>new</code> first.\n It\'s a built-in function that allocates memory, but unlike its namesakes\n in some other languages it does not <em>initialize</em> the memory,\n-it only <em>zeroes</em> it.\n+it only <em>zeros</em> it.\n That is,\n <code>new(T)</code> allocates zeroed storage for a new item of type\n <code>T</code> and returns its address, a value of type <code>*T</code>.\n    ```
    
    
  3. String() メソッドと fmt.Sprintf の再帰に関する説明の追加:

    --- a/doc/effective_go.html
    +++ b/doc/effective_go.html
    @@ -1697,13 +1716,20 @@ automatically for printing, even as part of a general type.\n </p>\n {{code \"/doc/progs/eff_bytesize.go\" `/^func.*ByteSize.*String/` `/^}/`}}\n <p>\n-(The <code>float64</code> conversions prevent <code>Sprintf</code> \n-from recurring back through the <code>String</code> method for \n-<code>ByteSize</code>.)\n+<p>\n+Note that it\'s fine to call <code>Sprintf</code> and friends in the\n+implementation of <code>String</code> methods, but beware of\n+recurring into the <code>String</code> method through the nested\n+<code>Sprintf</code> call using a string format\n+(<code>%s</code>, <code>%q</code>, <code>%v</code>, <code>%x</code> or <code>%X</code>).\n+The <code>ByteSize</code> implementation of <code>String</code> is safe\n+because it calls <code>Sprintf</code> with <code>%f</code>.\n+</p>\n+\n <h3 id=\"variables\">Variables</h3>\n \n <p>\n    ```
    
    

doc/progs/eff_bytesize.go の変更

eff_bytesize.go では、ByteSize 型の String() メソッドから float64() への明示的な型変換が削除されています。

--- a/doc/progs/eff_bytesize.go
+++ b/doc/progs/eff_bytesize.go
@@ -23,23 +23,23 @@ const (
 func (b ByteSize) String() string {
 	switch {
 	case b >= YB:
-		return fmt.Sprintf("%.2fYB", float64(b/YB))
+		return fmt.Sprintf("%.2fYB", b/YB)
 	case b >= ZB:
-		return fmt.Sprintf("%.2fZB", float64(b/ZB))
+		return fmt.Sprintf("%.2fZB", b/ZB)
 	case b >= EB:
-		return fmt.Sprintf("%.2fEB", float64(b/EB))
+		return fmt.Sprintf("%.2fEB", b/EB)
 	case b >= PB:
-		return fmt.Sprintf("%.2fPB", float64(b/PB))
+		return fmt.Sprintf("%.2fPB", b/PB)
 	case b >= TB:
-		return fmt.Sprintf("%.2fTB", float64(b/TB))
+		return fmt.Sprintf("%.2fTB", b/TB)
 	case b >= GB:
-		return fmt.Sprintf("%.2fGB", float64(b/GB))
+		return fmt.Sprintf("%.2fGB", b/GB)
 	case b >= MB:
-		return fmt.Sprintf("%.2fMB", float64(b/MB))
+		return fmt.Sprintf("%.2fMB", b/MB)
 	case b >= KB:
-		return fmt.Sprintf("%.2fKB", float64(b/KB))
+		return fmt.Sprintf("%.2fKB", b/KB)
 	}
-	return fmt.Sprintf("%.2fB", float64(b))
+	return fmt.Sprintf("%.2fB", b)
 }
 
 func main() {

コアとなるコードの解説

doc/effective_go.html の変更解説

  1. range ループの例の拡充: 追加されたマップの反復処理の例は、for key, value := range oldMap の形式でマップのキーと値の両方を取得できることを示しています。これは、マップの要素を別のマップにコピーする一般的なパターンです。 for key := range m の例は、マップのキーのみが必要な場合に、2番目の変数を省略できることを示しています。これは、マップ内に特定のキーが存在するかどうかをチェックし、そのキーに関連する操作(例: delete(m, key)) を行う際によく使われます。 for _, value := range array への変更は、ブランク識別子 _ の使用法をより明確にしています。以前の例ではマップに対して _ を使ってキーを無視していましたが、新しい例では配列に対してインデックスを無視して値のみを処理するパターンを示しています。これにより、range が配列/スライスとマップで異なる戻り値を持つこと、そして _ が不要な要素を破棄するために汎用的に使えることが強調されます。

  2. new キーワードの説明の修正: 「zeroes」から「zeros」への修正は、単なる英語のスペル修正です。Go言語の new 関数がメモリをゼロ値で初期化するという本質的な意味は変わりません。

  3. String() メソッドと fmt.Sprintf の再帰に関する説明の追加: この変更は、fmt パッケージの改善を反映した最も重要な部分です。以前は ByteSizeString() メソッドで float64 への明示的な型変換が必要だった理由が、SprintfString() メソッドを再帰的に呼び出すのを防ぐためであると説明されていました。 新しい説明では、String() メソッド内で Sprintf を呼び出すこと自体は問題ないが、%s, %q, %v, %x, %X のような「文字列フォーマット」指定子を使用すると、Sprintf が再び String() メソッドを呼び出し、無限再帰に陥る可能性があると警告しています。そして、ByteSize の例が %f (浮動小数点数フォーマット) を使用しているため安全であると明確に述べています。これは、fmt パッケージがカスタム型を処理する際に、文字列としてフォーマットする必要がある場合にのみ String() メソッドを呼び出すという、より洗練された挙動をするようになったことを示唆しています。開発者は、String() メソッド内で Sprintf を使用する際に、再帰を引き起こす可能性のあるフォーマット指定子を避けるべきであるという重要なガイダンスが提供されています。

doc/progs/eff_bytesize.go の変更解説

ByteSize 型の String() メソッドから float64() への明示的な型変換がすべて削除されました。

func (b ByteSize) String() string {
	switch {
	case b >= YB:
		return fmt.Sprintf("%.2fYB", b/YB) // 以前は float64(b/YB)
	case b >= ZB:
		return fmt.Sprintf("%.2fZB", b/ZB) // 以前は float64(b/ZB)
	// ... 他のケースも同様
	}
	return fmt.Sprintf("%.2fB", b) // 以前は float64(b)
}

この変更は、fmt パッケージが ByteSize 型を %f フォーマット指定子で直接処理できるようになったことを示しています。ByteSize は基底型が数値型(float64)であるため、fmt.SprintfString() メソッドを呼び出すことなく、直接数値としてフォーマットできます。以前の float64() への変換は、fmt パッケージの挙動がまだ洗練されていなかった時期の回避策であり、このコミットによってその回避策が不要になったことを示しています。これにより、コードがより簡潔になり、fmt パッケージの進化が反映されています。

関連リンク

参考にした情報源リンク