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

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

このコミットは、Go言語の標準ライブラリの一部である tabwriter パッケージに対する変更です。tabwriter パッケージは、テキストを整形してタブ区切りの列をきれいに揃えるためのユーティリティを提供します。このコミットの主な目的は、tabwriter.Writer 構造体の初期化パラメータを簡素化し、将来の拡張性を高めることです。具体的には、複数のブーリアンフラグを単一の uint 型のフラグフィールドに統合しています。

コミット

commit 6906e3b884ad1c7890ee9c05c2fcbb44a8d173b9
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Mar 10 16:30:26 2009 -0700

    - incorporate suggestions from previous code review
    
    R=rsc
    DELTA=64  (18 added, 3 deleted, 43 changed)
    OCL=26046
    CL=26058
---
 src/lib/tabwriter/tabwriter.go      | 43 +++++++++++++++++---------\
 src/lib/tabwriter/tabwriter_test.go | 60 ++++++++++++++++++-------------------\
 2 files changed, 59 insertions(+), 44 deletions(-)

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

https://github.com/golang/go/commit/6906e3b884ad1c7890ee9c05c2fcbb44a8d173b9

元コミット内容

このコミットは、以前のコードレビューで指摘された改善提案を取り入れたものです。具体的な変更内容は、tabwriter.Writer 構造体の align_left (左寄せ) と filter_html (HTMLタグフィルタリング) という2つのブーリアンフィールドを、flags という単一の uint 型フィールドに置き換えることです。これにより、Init 関数および New (後に NewWriter に改名) 関数のシグネチャが簡素化され、新しいフォーマットオプションを追加する際の柔軟性が向上します。

変更の背景

Go言語の初期開発段階では、API設計の試行錯誤が頻繁に行われていました。このコミットが行われた2009年3月は、Go言語が一般に公開される前の時期であり、標準ライブラリのAPIが活発に開発・改善されていた時期です。

tabwriter パッケージの Writer 構造体には、当初 align_leftfilter_html という2つのブーリアンフィールドがありました。これらのフィールドは、それぞれセルのアライメントとHTMLタグの処理を制御していました。しかし、このようなブーリアンフラグが複数ある場合、以下のような問題が生じます。

  1. 関数のシグネチャの複雑化: Init 関数やコンストラクタ関数 (New) の引数リストが長くなり、可読性が低下します。
  2. 拡張性の低さ: 新しいオプションを追加するたびに、構造体に新しいブーリアンフィールドを追加し、関連する関数のシグネチャを変更する必要があり、APIの破壊的変更につながる可能性があります。
  3. オプションの組み合わせの管理: 複数のブーリアンフラグの組み合わせによっては、意味のない、あるいは矛盾する状態が発生する可能性があります。

これらの問題を解決し、よりクリーンで拡張性の高いAPIを提供するために、コードレビューでブーリアンフラグをビットフラグに統合する提案がなされたと考えられます。これにより、将来的に新しいフォーマットオプションが追加された場合でも、既存のAPIを変更することなく、新しいフラグを追加するだけで対応できるようになります。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と一般的なプログラミングの知識が必要です。

  1. Go言語の構造体 (struct): 複数のフィールド(変数)をまとめた複合データ型です。tabwriter.Writer はこの構造体の一例です。
  2. Go言語のメソッド: 構造体に関連付けられた関数です。InitWriteWriter 構造体のメソッドです。
  3. Go言語のインターフェース (interface): 型が満たすべき振る舞いを定義します。io.WriteWrite メソッドを持つ型が満たすべきインターフェースです。
  4. ビットフラグ (Bit Flags): 複数のブーリアン状態を単一の整数型(この場合は uint)の各ビットに対応させて表現する手法です。各ビットが特定のオプションのオン/オフを示します。
    • ビット演算子:
      • | (ビットOR): 複数のフラグを組み合わせる際に使用します。例: FlagA | FlagB
      • & (ビットAND): 特定のフラグが設定されているかを確認する際に使用します。例: flags & FlagA != 0
      • ^ (ビットXOR): ビットを反転させる際に使用します。このコミットでは ^AlignRight のように使用されており、これは AlignRight 以外のすべてのビットを立てる(反転させる)ことを意味します。
      • << (左シフト): ビットを左にシフトすることで、2のべき乗を表現します。1 << iota のように使用されます。
  5. iota キーワード: Go言語の定数宣言で使用される特殊な識別子です。const ブロック内で使用され、連続する定数に自動的にインクリメントされる値を割り当てます。デフォルトでは0から始まり、各 const 定義で1ずつ増加します。これにより、ビットフラグの値を簡単に定義できます。
    const (
        FlagA = 1 << iota // FlagA = 1 (0001)
        FlagB             // FlagB = 2 (0010)
        FlagC             // FlagC = 4 (0100)
    )
    
  6. panicos.Error: Go言語の初期のエラーハンドリングは、現在のような error インターフェースが確立される前であり、os.Error という具体的な型が使われていました。panic は回復不可能なエラーを示すために使用されます。
  7. tabwriter パッケージの機能:
    • タブ区切りテキストの整形: 入力ストリームからタブ (\t) で区切られたテキストを読み込み、出力ストリームに整形された形で書き出します。
    • 列の自動調整: 各列の幅を自動的に計算し、最も長い要素に合わせて調整することで、きれいに揃った表形式の出力を生成します。
    • パディング: 列の間に指定されたパディング文字(例: スペース、ドット)を挿入します。
    • アライメント: 各セルの内容を左寄せまたは右寄せに設定できます。
    • HTMLタグのフィルタリング: HTMLタグを無視したり、HTMLエンティティを単一の文字として扱ったりするオプションがあります。

技術的詳細

このコミットの技術的な核心は、tabwriter.Writer 構造体の内部状態管理の変更と、それに伴うAPIの調整です。

  1. Writer 構造体の変更:

    • 削除: align_left bool, filter_html bool
    • 追加: flags uint この変更により、Writer 構造体のメモリフットプリントがわずかに削減され、複数のブーリアンフラグを個別に管理する手間がなくなります。
  2. フラグ定数の導入: const ブロック内で iota を使用して、FilterHTMLAlignRight というビットフラグ定数が定義されました。

    const (
        // Ignore html tags and treat entities (starting with '&'
        // and ending in ';') as single characters (width = 1).
        FilterHTML = 1 << iota; // 1 (0001)
    
        // Force right-alignment of cell content.
        // Default is left-alignment.
        AlignRight;             // 2 (0010)
    )
    
    • FilterHTML = 1 << iotaiota が0なので 1 << 0、つまり 1 となります。
    • AlignRightiota が1にインクリメントされるため、1 << 1、つまり 2 となります。 これにより、FilterHTML は1番目のビット、AlignRight は2番目のビットに対応するようになります。
  3. Init 関数のシグネチャ変更:

    • 変更前: func (b *Writer) Init(output io.Write, cellwidth, padding int, padchar byte, align_left, filter_html bool) *Writer
    • 変更後: func (b *Writer) Init(output io.Write, cellwidth, padding int, padchar byte, flags uint) *Writer align_leftfilter_html の2つのブーリアン引数が、単一の flags uint 引数に置き換えられました。これにより、関数の引数が減り、より簡潔になりました。
  4. Init 関数内のフラグ処理ロジック:

    • padchar == '\t' の場合の特殊処理: tabwriter は、パディング文字としてタブ (\t) が指定された場合、強制的に左寄せになります。以前は b.align_left = align_left || padchar == '\t' で処理されていましたが、新しいフラグシステムでは flags &= uint(t) のようにビット演算で AlignRight フラグをクリアしています。 t := ^AlignRightAlignRight のビットを反転させることで、AlignRight 以外のすべてのビットが1になるマスクを作成します。このマスクと flags をビットAND (&) することで、AlignRight フラグが強制的に0に設定されます。コメントにある // TODO 6g bug は、当時のGoコンパイラ (6g) のバグに対するワークアラウンドを示唆している可能性があります。
    • b.flags = flags で、渡されたフラグが Writer 構造体に設定されます。
  5. writeLines メソッド内のアライメント処理: 以前は if b.align_left でアライメントを分岐していましたが、switch ステートメントとビットAND演算 (b.flags & AlignRight != 0) を使用して、AlignRight フラグが設定されているかどうかで右寄せ/左寄せを判断するように変更されました。

    • default: // align left は、AlignRight フラグが設定されていない場合のデフォルトの動作として左寄せを意味します。
    • case b.flags & AlignRight != 0: // align right は、AlignRight フラグが設定されている場合に右寄せのロジックを実行します。
  6. Write メソッド内のHTMLフィルタリング処理: 以前は if b.filter_html でHTMLフィルタリングを分岐していましたが、if b.flags & FilterHTML != 0 のようにビットAND演算を使用して、FilterHTML フラグが設定されているかどうかでフィルタリングを判断するように変更されました。

  7. New 関数の改名とシグネチャ変更:

    • 変更前: func New(writer io.Write, cellwidth, padding int, padchar byte, align_left, filter_html bool) *Writer
    • 変更後: func NewWriter(writer io.Write, cellwidth, padding int, padchar byte, flags uint) *Writer 関数名が New から NewWriter に変更されました。これは、Go言語の慣例として、構造体名を省略せずにコンストラクタ関数に含めることが推奨されるようになったためと考えられます。また、Init 関数と同様に、引数が単一の flags uint に変更されました。
  8. テストファイルの変更 (tabwriter_test.go): check 関数のシグネチャも、align_left, filter_html bool から flags uint に変更されました。これにより、テストケースの呼び出しも新しいフラグ形式に更新されています。 例えば、true, false (左寄せ、HTMLフィルタなし) は 0 (デフォルトフラグなし) に、true, true (左寄せ、HTMLフィルタあり) は tabwriter.FilterHTML に、false, false (右寄せ、HTMLフィルタなし) は tabwriter.AlignRight にそれぞれ置き換えられています。

これらの変更は、APIの整合性を高め、将来の機能追加に対する柔軟性を提供することを目的としています。

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

src/lib/tabwriter/tabwriter.go

--- a/src/lib/tabwriter/tabwriter.go
+++ b/src/lib/tabwriter/tabwriter.go
@@ -99,8 +99,7 @@ type Writer struct {
 	cellwidth int;
 	padding int;
 	padbytes [8]byte;
-	align_left bool;
-	filter_html bool;
+	flags uint;

 	// current state
 	thtml_char byte;  // terminating char of html tag/entity, or 0 ('>', ';', or 0)
@@ -144,6 +143,18 @@ func (b *Writer) addLine() {
 }


+// Formatting can be controlled with these flags.
+const (
+// Ignore html tags and treat entities (starting with '&'
+// and ending in ';') as single characters (width = 1).
+	FilterHTML = 1 << iota;
+
+// Force right-alignment of cell content.
+// Default is left-alignment.
+	AlignRight;
+)
+
+
 // A Writer must be initialized with a call to Init. The first parameter (output)
 // specifies the filter output. The remaining parameters control the formatting:
 //
@@ -155,11 +166,9 @@ func (b *Writer) addLine() {
 //			and cells are left-aligned independent of align_left
 //			(for correct-looking results, cellwidth must correspond
 //			to the tab width in the viewer displaying the result)
-//	align_left	alignment of cell content
-//	filter_html	ignores html tags and treats entities (starting with '&'
-//			and ending in ';') as single characters (width = 1)
+//	flags		formatting control
 //
-func (b *Writer) Init(output io.Write, cellwidth, padding int, padchar byte, align_left, filter_html bool) *Writer {
+func (b *Writer) Init(output io.Write, cellwidth, padding int, padchar byte, flags uint) *Writer {
 	if cellwidth < 0 {
 		panic("negative cellwidth");
 	}
@@ -172,8 +181,12 @@ func (b *Writer) Init(output io.Write, cellwidth, padding int, padchar byte, ali
 	for i := len(b.padbytes) - 1; i >= 0; i-- {
 		b.padbytes[i] = padchar;
 	}
-	b.align_left = align_left || padchar == '\t';  // tab enforces left-alignment
-	b.filter_html = filter_html;
+	if padchar == '\t' {
+		// tab enforces left-alignment
+		t := ^AlignRight;  // TODO 6g bug
+		flags &= uint(t);
+	}
+	b.flags = flags;

 	b.buf.Init(1024);
 	b.lines_size.Init(0);
@@ -256,7 +269,9 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int, err *os.Error)
 		for j := 0; j < line_size.Len(); j++ {
 			s, w := line_size.At(j), line_width.At(j);

-			if b.align_left {
+			switch {
+			default: // align left
+
 				err = b.write0(b.buf.slice(pos, pos + s));
 				if err != nil {
 					goto exit;
@@ -269,7 +284,7 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int, err *os.Error)
 					}
 				}

-			} else {  // align right
+			case b.flags & AlignRight != 0:  // align right

 				if j < b.widths.Len() {
 					err = b.writePadding(w, b.widths.At(j));
@@ -433,7 +448,7 @@ func (b *Writer) Write(buf []byte) (written int, err *os.Error) {
 				}

 			case '<', '&':
-				if b.filter_html {
+				if b.flags & FilterHTML != 0 {
 					b.append(buf[i0 : i]);
 					i0 = i;
 					b.width += unicodeLen(b.buf.slice(b.pos, b.buf.Len()));
@@ -467,9 +482,9 @@ func (b *Writer) Write(buf []byte) (written int, err *os.Error) {
 }


-// New allocates and initializes a new tabwriter.Writer.
+// NewWriter allocates and initializes a new tabwriter.Writer.
 // The parameters are the same as for the the Init function.
 //
-func New(writer io.Write, cellwidth, padding int, padchar byte, align_left, filter_html bool) *Writer {
-	return new(Writer).Init(writer, cellwidth, padding, padchar, align_left, filter_html)
+func NewWriter(writer io.Write, cellwidth, padding int, padchar byte, flags uint) *Writer {
+	return new(Writer).Init(writer, cellwidth, padding, padchar, flags)
 }

src/lib/tabwriter/tabwriter_test.go

--- a/src/lib/tabwriter/tabwriter_test.go
+++ b/src/lib/tabwriter/tabwriter_test.go
@@ -71,12 +71,12 @@ func verify(t *testing.T, w *tabwriter.Writer, b *buffer, src, expected string)
 }


-func check(t *testing.T, tabwidth, padding int, padchar byte, align_left, filter_html bool, src, expected string) {
+func check(t *testing.T, tabwidth, padding int, padchar byte, flags uint, src, expected string) {
 	var b buffer;
 	b.init(1000);

 	var w tabwriter.Writer;
-	w.Init(&b, tabwidth, padding, padchar, align_left, filter_html);
+	w.Init(&b, tabwidth, padding, padchar, flags);

 	// write all at once
 	b.clear();
@@ -105,109 +105,109 @@ func check(t *testing.T, tabwidth, padding int, padchar byte, align_left, filter
 
 func Test(t *testing.T) {
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"",
 		""
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"\n\n\n",
 		"\n\n\n"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"a\nb\nc",
 		"a\nb\nc"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"\t",  // '\t' terminates an empty cell on last line - nothing to print
 		""
 	);
 
 	check(
-		t, 8, 1, '.', false, false,
+		t, 8, 1, '.', tabwriter.AlignRight,
 		"\t",  // '\t' terminates an empty cell on last line - nothing to print
 		""
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"*\t*",
 		"**"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"*\t*\n",
 		"*.......*\n"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"*\t*\t",
 		"*.......*"
 	);
 
 	check(
-		t, 8, 1, '.', false, false,
+		t, 8, 1, '.', tabwriter.AlignRight,
 		"*\t*\t",
 		".......**"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"\t\n",
 		"........\n"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"a) foo",
 		"a) foo"
 	);
 
 	check(
-		t, 8, 1, ' ', true, false,
+		t, 8, 1, ' ', 0,
 		"b) foo\tbar",  // "bar" is not in any cell - not formatted, just flushed
 		"b) foobar"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"c) foo\tbar\t",
 		"c) foo..bar"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"d) foo\tbar\n",
 		"d) foo..bar\n"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"e) foo\tbar\t\n",
 		"e) foo..bar.....\n"
 	);
 
 	check(
-		t, 8, 1, '.', true, true,
+		t, 8, 1, '.', tabwriter.FilterHTML,
 		"e) f&lt;o\t<b>bar</b>\t\n",
 		"e) f&lt;o..<b>bar</b>.....\n"
 	);
 
 	check(
-		t, 8, 1, '*', true, false,
+		t, 8, 1, '*', 0,
 		"Hello, world!\n",
 		"Hello, world!\n"
 	);
 
 	check(
-		t, 0, 0, '.', true, false,
+		t, 0, 0, '.', 0,
 		"1\t2\t3\t4\n"
 		"11\t222\t3333\t44444\n",
 
@@ -216,19 +216,19 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 5, 0, '.', true, false,
+		t, 5, 0, '.', 0,
 		"1\t2\t3\t4\n",
 		"1....2....3....4\n"
 	);
 
 	check(
-		t, 5, 0, '.', true, false,
+		t, 5, 0, '.', 0,
 		"1\t2\t3\t4\t\n",
 		"1....2....3....4....\n"
 	);
 
 	check(
-		t, 8, 1, '.', true, false,
+		t, 8, 1, '.', 0,
 		"本\tb\tc\n"
 		"aa\t本本本\tcccc\tddddd\n"
 		"aaa\tbbbb\n",
@@ -239,7 +239,7 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 8, 1, ' ', false, false,
+		t, 8, 1, ' ', tabwriter.AlignRight,
 		"a\tè\tc\t\n"
 		"aa\tèèè\tcccc\tddddd\t\n"
 		"aaa\tèèèè\t\n",
@@ -250,7 +250,7 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 2, 0, ' ', true, false,
+		t, 2, 0, ' ', 0,
 		"a\tb\tc\n"
 		"aa\tbbb\tcccc\n"
 		"aaa\tbbbb\n",
@@ -261,7 +261,7 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 8, 1, '_', true, false,
+		t, 8, 1, '_', 0,
 		"a\tb\tc\n"
 		"aa\tbbb\tcccc\n"
 		"aaa\tbbbb\n",
@@ -272,7 +272,7 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 4, 1, '-', true, false,
+		t, 4, 1, '-', 0,
 		"4444\t日本語\t22\t1\t333\n"
 		"999999999\t22\n"
 		"7\t22\n",
@@ -291,7 +291,7 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 4, 3, '.', true, false,
+		t, 4, 3, '.', 0,
 		"4444\t333\t22\t1\t333\n"
 		"999999999\t22\n"
 		"7\t22\n",
@@ -310,7 +310,7 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 8, 1, '\t', true, true,
+		t, 8, 1, '\t', tabwriter.FilterHTML,
 		"4444\t333\t22\t1\t333\n"
 		"999999999\t22\n"
 		"7\t22\n",
@@ -329,7 +329,7 @@ func Test(t *testing.T) {
 	);
 
 	check(
-		t, 0, 2, ' ', false, false,
+		t, 0, 2, ' ', tabwriter.AlignRight,
 		".0\t.3\t2.4\t-5.1\t\n"
 		"23.0\t12345678.9\t2.4\t-989.4\t\n"
 		"5.1\t12.0\t2.4\t-7.0\t\n"

コアとなるコードの解説

このコミットのコアとなる変更は、tabwriter.Writer 構造体の設計思想の転換にあります。

  1. Writer 構造体フィールドの集約:

    • align_left boolfilter_html bool という個別のブーリアンフィールドが削除され、代わりに flags uint という単一の uint 型フィールドが導入されました。
    • これにより、Writer 構造体の定義がより簡潔になり、将来的に新しいフォーマットオプションを追加する際に、既存の構造体定義を変更する必要がなくなります。新しいオプションは、新しいビットフラグ定数として追加するだけで済みます。
  2. ビットフラグ定数の定義と iota の活用:

    const (
        FilterHTML = 1 << iota;
        AlignRight;
    )
    
    • iota はGo言語の定数宣言で連続する値を自動的に生成する特殊な識別子です。
    • FilterHTML = 1 << iota では、iota0 なので 1 << 0、つまり 1 (バイナリで 0001) となります。
    • AlignRight では、iota が自動的に 1 にインクリメントされるため、1 << 1、つまり 2 (バイナリで 0010) となります。
    • このように、各フラグが異なるビット位置に対応するように定義することで、複数のオプションを単一の uint 値にパックできます。
  3. Init 関数の引数と内部ロジックの変更:

    • Init 関数のシグネチャが (..., align_left, filter_html bool) から (..., flags uint) に変更されました。これにより、関数の呼び出しがより簡潔になり、可読性が向上します。
    • 内部では、渡された flags 値が b.flags に直接代入されます。
    • padchar == '\t' の場合の特殊処理 (t := ^AlignRight; flags &= uint(t);) は、パディング文字がタブの場合に強制的に左寄せにするためのものです。^AlignRightAlignRight 以外のすべてのビットが1であるマスクを生成し、これと flags をビットANDすることで、AlignRight ビットが強制的に0に設定されます。これは、タブによるパディングが常に左寄せを意味するという tabwriter の設計上の制約を反映しています。
  4. writeLines メソッドでのアライメント処理の変更:

    • 以前は if b.align_left で条件分岐していましたが、switch ステートメントと b.flags & AlignRight != 0 というビットAND演算を使用して、AlignRight フラグが設定されているかどうかでアライメントを決定するように変更されました。
    • b.flags & AlignRight != 0 は、flags の中に AlignRight ビットが立っているかどうかを効率的にチェックします。この結果が true であれば右寄せ、false であれば default ケース(左寄せ)が実行されます。
  5. Write メソッドでのHTMLフィルタリング処理の変更:

    • 同様に、if b.filter_html から if b.flags & FilterHTML != 0 に変更され、FilterHTML フラグの有無でHTMLフィルタリングのロジックが実行されるようになりました。
  6. New から NewWriter への改名:

    • Go言語の慣例として、コンストラクタ関数は New の後に構造体名を続ける形式 (NewWriter) が推奨されるため、関数名が変更されました。これはAPIの整合性を高めるための変更です。

これらの変更は、tabwriter パッケージの内部実装をより堅牢で拡張性の高いものにし、同時に外部から利用するAPIをよりクリーンで理解しやすいものにすることを目的としています。ビットフラグの利用は、複数のブーリアンオプションを効率的に管理するための一般的なパターンであり、Go言語の標準ライブラリでも広く採用されています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記リンクを含む)
  • Go言語の初期のコミット履歴 (GitHub)
  • ビットフラグに関する一般的なプログラミングの概念
  • Go言語のコードレビュー文化とAPI設計の進化に関する一般的な知識