[インデックス 18903] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbytesパッケージとstringsパッケージにおけるReader型のReadAtメソッドの挙動に関する修正です。具体的には、ReadAtメソッドがレシーバ(Readerのインスタンス)を予期せず変更(mutate)しないように、以前の変更を部分的に元に戻すものです。これにより、ReadAtがレシーバをミューテートしないことを前提としていたテストにおけるデータ競合の問題が解決されました。
コミット
commit cc4bdf0226f192432a0d7c95b02cf3ecced81c15
Author: Rui Ueyama <ruiu@google.com>
Date: Wed Mar 19 12:13:47 2014 -0700
strings, bytes: ReadAt should not mutate receiver
CL 77580046 caused a data race issue with tests that assumes ReadAt
does not mutate receiver. This patch partially revert CL 77580046
to fix it.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/77900043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cc4bdf0226f192432a0d7c95b02cf3ecced81c15
元コミット内容
このコミットは、以前の変更であるCL 77580046によって導入された問題を修正するために、その変更を部分的に元に戻すものです。CL 77580046の具体的な内容は、このコミットメッセージからは直接読み取れませんが、ReadAtメソッドがレシーバをミューテートするような変更を含んでいたと推測されます。
このコミットでは、以下の4つのファイルから合計4行の削除が行われています。
src/pkg/bytes/reader.go:ReadAtメソッドからr.prevRune = -1の行を削除。src/pkg/bytes/reader_test.go:UnreadRuneErrorTestsから"ReadAt"に関連するテストケースを削除。src/pkg/strings/reader.go:ReadAtメソッドからr.prevRune = -1の行を削除。src/pkg/strings/strings_test.go:UnreadRuneErrorTestsから"ReadAt"に関連するテストケースを削除。
変更の背景
変更の背景には、CL 77580046という以前のコミットが深く関わっています。このCL 77580046によって、bytes.Readerとstrings.ReaderのReadAtメソッドにr.prevRune = -1という行が追加されました。この行は、ReadAtが呼び出された際に、Readerの内部状態であるprevRune(直前に読み込んだルーンに関する情報)をリセットする役割を持っていました。
しかし、ReadAtメソッドは通常、ランダムアクセス読み取りを行うためのものであり、ストリームの現在位置や直前のルーンといった内部状態を変更すべきではありません。なぜなら、ReadAtは複数のゴルーチンから同時に呼び出される可能性があるため、内部状態を変更するとデータ競合(data race)が発生するリスクがあるからです。
このCL 77580046の変更により、ReadAtがレシーバをミューテートしないことを前提としていた既存のテストでデータ競合が発生するようになりました。このコミットは、そのデータ競合を修正するために、CL 77580046で追加されたr.prevRune = -1の行を削除し、ReadAtメソッドがレシーバをミューテートしない元の挙動に戻すことを目的としています。
前提知識の解説
Go言語のio.Readerとio.ReaderAtインターフェース
Go言語では、データの読み取り操作を抽象化するためにio.Readerとio.ReaderAtという重要なインターフェースが定義されています。
-
io.Reader:type Reader interface { Read(p []byte) (n int, err error) }Readメソッドは、データをストリームから読み取り、pに書き込みます。読み取り操作は通常、ストリームの現在位置を進めます。これはシーケンシャルな読み取りに適しています。 -
io.ReaderAt:type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) }ReadAtメソッドは、指定されたオフセットoffからデータを読み取り、pに書き込みます。ReadAtの重要な特性は、ストリームの現在位置を変更しないことです。これにより、複数のゴルーチンが同時に異なるオフセットからデータを読み取ることが可能になり、並行処理において非常に有用です。ファイルやメモリ上のデータなど、ランダムアクセスが可能なデータソースに対して実装されます。
bytes.Readerとstrings.Reader
Goの標準ライブラリには、バイトスライスや文字列をio.Readerインターフェースとして扱うための具体的な実装が提供されています。
bytes.Reader:[]byte(バイトスライス)を読み取り可能なストリームとして扱います。strings.Reader:string(文字列)を読み取り可能なストリームとして扱います。
これらのReader型は、内部に読み取り位置や、場合によっては直前に読み込んだルーン(Unicodeコードポイント)に関する情報(prevRuneなど)を保持しています。
データ競合 (Data Race)
データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって順序付けされていない場合に発生するプログラミング上のバグです。データ競合が発生すると、プログラムの動作が予測不能になったり、クラッシュしたりする可能性があります。
ReadAtメソッドは、その性質上、並行して呼び出されることが想定されるため、内部状態をミューテート(変更)するとデータ競合のリスクが高まります。
r.prevRune = -1 の意味
r.prevRuneは、bytes.Readerやstrings.Readerが内部的に保持するフィールドで、UnreadRuneメソッド(直前に読み込んだルーンをストリームに戻す)をサポートするために、直前に読み込んだルーンの情報を格納します。-1は、有効なルーンが読み込まれていない状態を示すことが多いです。
ReadAtメソッド内でr.prevRune = -1と設定することは、ReadAtが呼び出された際に、UnreadRuneの内部状態をリセットすることを意味します。しかし、前述の通り、ReadAtはストリームの現在位置やその他の内部状態を変更すべきではないため、この操作はReadAtのセマンティクスに反し、並行処理における問題を引き起こす可能性がありました。
技術的詳細
このコミットの技術的な核心は、io.ReaderAtインターフェースのセマンティクスを厳密に遵守することにあります。io.ReaderAtのドキュメントには、「ReadAtは、基礎となる入力ストリームのオフセットを変更しない」と明記されています。この「オフセット」は、単に読み取り位置だけでなく、Readerが持つ他の内部状態(例えばprevRune)にも適用されるべきであるという解釈が、この修正の根底にあります。
CL 77580046では、bytes.Readerとstrings.ReaderのReadAtメソッドに以下の行が追加されました。
r.prevRune = -1
この変更は、ReadAtが呼び出された際に、ReaderのprevRuneフィールドを-1にリセットすることを意味します。prevRuneは、UnreadRuneメソッドが正しく機能するために、直前に読み込まれたルーンの情報を保持する内部状態です。
しかし、ReadAtはランダムアクセスを目的としたメソッドであり、ストリームの現在位置やUnreadRuneの状態のような内部状態を変更すべきではありません。もし複数のゴルーチンが同時にReadAtを呼び出し、その中でr.prevRuneが変更されると、UnreadRuneを使用する他の操作(例えば、同じReaderインスタンスに対してReadやUnreadRuneを呼び出すゴルーチン)との間でデータ競合が発生する可能性がありました。特に、ReadAtがprevRuneをリセットすることで、UnreadRuneが期待する値を見つけられなくなり、テストが失敗する原因となりました。
このコミットでは、CL 77580046で追加されたr.prevRune = -1の行を削除することで、ReadAtメソッドがReaderの内部状態を一切変更しないように修正しています。これにより、ReadAtの呼び出しが副作用を持たなくなり、並行処理環境下での安全性が確保されます。
また、テストファイル(reader_test.goとstrings_test.go)からReadAtに関連するテストケースが削除されているのは、おそらくUnreadRuneErrorTestsというテストスイートが、ReadAtがprevRuneをリセットするという誤った前提に基づいて設計されていたためと考えられます。ReadAtがprevRuneをリセットしないように修正されたため、その前提に基づくテストは不要になったか、あるいは不適切になったため削除されたと推測されます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、以下の2つのファイルにおけるReadAtメソッドからの1行の削除です。
-
src/pkg/bytes/reader.go--- a/src/pkg/bytes/reader.go +++ b/src/pkg/bytes/reader.go @@ -43,7 +43,6 @@ func (r *Reader) Read(b []byte) (n int, err error) { } func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) { - r.prevRune = -1 if off < 0 { return 0, errors.New("bytes: invalid offset") } -
src/pkg/strings/reader.go--- a/src/pkg/strings/reader.go +++ b/src/pkg/strings/reader.go @@ -42,7 +42,6 @@ func (r *Reader) Read(b []byte) (n int, err error) { } func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) { - r.prevRune = -1 if off < 0 { return 0, errors.New("strings: invalid offset") }
また、関連するテストファイルからもReadAtに関するテストケースが削除されています。
-
src/pkg/bytes/reader_test.go--- a/src/pkg/bytes/reader_test.go +++ b/src/pkg/bytes/reader_test.go @@ -138,7 +138,6 @@ var UnreadRuneErrorTests = []struct { f func(*Reader) }{ {"Read", func(r *Reader) { r.Read([]byte{}) }}, - {"ReadAt", func(r *Reader) { r.ReadAt([]byte{}, 0) }}, {"ReadByte", func(r *Reader) { r.ReadByte() }}, {"UnreadRune", func(r *Reader) { r.UnreadRune() }}, {"Seek", func(r *Reader) { r.Seek(0, 1) }}, -
src/pkg/strings/strings_test.go--- a/src/pkg/strings/strings_test.go +++ b/src/pkg/strings/strings_test.go @@ -863,7 +863,6 @@ var UnreadRuneErrorTests = []struct { f func(*Reader) }{ {"Read", func(r *Reader) { r.Read([]byte{}) }}, - {"ReadAt", func(r *Reader) { r.ReadAt([]byte{}, 0) }}, {"ReadByte", func(r *Reader) { r.ReadByte() }}, {"UnreadRune", func(r *Reader) { r.UnreadRune() }}, {"Seek", func(r *Reader) { r.Seek(0, 1) }},
コアとなるコードの解説
このコミットの核心は、ReadAtメソッドが「レシーバをミューテートしない」というio.ReaderAtインターフェースの重要な契約を再確立することです。
bytes.Readerとstrings.ReaderのReadAtメソッドからr.prevRune = -1という行が削除されたことで、ReadAtの呼び出しはReaderインスタンスのprevRuneフィールドに影響を与えなくなりました。これにより、以下の重要な点が保証されます。
- 副作用の排除:
ReadAtは、データの読み取り以外の副作用(この場合は内部状態の変更)を持たなくなります。これは、関数型プログラミングの原則にも通じる、より予測可能で安全なコードの実現に貢献します。 - 並行処理の安全性向上: 複数のゴルーチンが同じ
Readerインスタンスに対して同時にReadAtを呼び出しても、prevRuneフィールドを巡るデータ競合が発生しなくなります。ReadAtはオフセット指定によるランダムアクセスを意図しているため、並行アクセスされることが多く、この変更は並行処理の堅牢性を高めます。 io.ReaderAtセマンティクスの遵守:io.ReaderAtインターフェースのドキュメントに明記されている「基礎となる入力ストリームのオフセットを変更しない」という契約を、より広範な意味で遵守することになります。これは、単に読み取り位置だけでなく、UnreadRuneのような他の操作に影響を与える可能性のある内部状態も変更しないことを意味します。
テストファイルからのReadAt関連のテストケースの削除は、ReadAtがprevRuneをリセットするという誤った前提に基づいていたテストが、この修正によって不要になったか、あるいは不適切になったためと考えられます。ReadAtがprevRuneをリセットしないという正しい挙動になったため、その挙動を期待するテストはもはや意味をなさなくなった、と解釈できます。
この修正は、Go言語の標準ライブラリが提供するインターフェースのセマンティクスを厳密に守り、並行処理における潜在的なバグを未然に防ぐための重要なステップと言えます。
関連リンク
- Go言語の
ioパッケージドキュメント: https://pkg.go.dev/io - Go言語の
bytesパッケージドキュメント: https://pkg.go.dev/bytes - Go言語の
stringsパッケージドキュメント: https://pkg.go.dev/strings - Go言語におけるデータ競合の検出 (Go Race Detector): https://go.dev/doc/articles/race_detector
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
- Go言語の
io.ReaderAtインターフェースに関する一般的な解説 - データ競合に関する一般的なプログラミングの知識
- Go言語のコードレビューシステム (Gerrit) のCL (Change List) の概念
[インデックス 18903] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbytesパッケージとstringsパッケージにおけるReader型のReadAtメソッドの挙動に関する修正です。具体的には、ReadAtメソッドがレシーバ(Readerのインスタンス)を予期せず変更(mutate)しないように、以前の変更を部分的に元に戻すものです。これにより、ReadAtがレシーバをミューテートしないことを前提としていたテストにおけるデータ競合の問題が解決されました。
コミット
commit cc4bdf0226f192432a0d7c95b02cf3ecced81c15
Author: Rui Ueyama <ruiu@google.com>
Date: Wed Mar 19 12:13:47 2014 -0700
strings, bytes: ReadAt should not mutate receiver
CL 77580046 caused a data race issue with tests that assumes ReadAt
does not mutate receiver. This patch partially revert CL 77580046
to fix it.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/77900043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cc4bdf0226f192432a0d7c95b02cf3ecced81c15
元コミット内容
このコミットは、以前の変更であるCL 77580046によって導入された問題を修正するために、その変更を部分的に元に戻すものです。CL 77580046の具体的な内容は、このコミットメッセージからは直接読み取れませんが、ReadAtメソッドがレシーバをミューテートするような変更を含んでいたと推測されます。
このコミットでは、以下の4つのファイルから合計4行の削除が行われています。
src/pkg/bytes/reader.go:ReadAtメソッドからr.prevRune = -1の行を削除。src/pkg/bytes/reader_test.go:UnreadRuneErrorTestsから"ReadAt"に関連するテストケースを削除。src/pkg/strings/reader.go:ReadAtメソッドからr.prevRune = -1の行を削除。src/pkg/strings/strings_test.go:UnreadRuneErrorTestsから"ReadAt"に関連するテストケースを削除。
変更の背景
変更の背景には、CL 77580046という以前のコミットが深く関わっています。このCL 77580046によって、bytes.Readerとstrings.ReaderのReadAtメソッドにr.prevRune = -1という行が追加されました。この行は、ReadAtが呼び出された際に、Readerの内部状態であるprevRune(直前に読み込んだルーンに関する情報)をリセットする役割を持っていました。
しかし、ReadAtメソッドは通常、ランダムアクセス読み取りを行うためのものであり、ストリームの現在位置や直前のルーンといった内部状態を変更すべきではありません。なぜなら、ReadAtは複数のゴルーチンから同時に呼び出される可能性があるため、内部状態を変更するとデータ競合(data race)が発生するリスクがあるからです。
このCL 77580046の変更により、ReadAtがレシーバをミューテートしないことを前提としていた既存のテストでデータ競合が発生するようになりました。このコミットは、そのデータ競合を修正するために、CL 77580046で追加されたr.prevRune = -1の行を削除し、ReadAtメソッドがレシーバをミューテートしない元の挙動に戻すことを目的としています。
前提知識の解説
Go言語のio.Readerとio.ReaderAtインターフェース
Go言語では、データの読み取り操作を抽象化するためにio.Readerとio.ReaderAtという重要なインターフェースが定義されています。
-
io.Reader:type Reader interface { Read(p []byte) (n int, err error) }Readメソッドは、データをストリームから読み取り、pに書き込みます。読み取り操作は通常、ストリームの現在位置を進めます。これはシーケンシャルな読み取りに適しています。 -
io.ReaderAt:type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) }ReadAtメソッドは、指定されたオフセットoffからデータを読み取り、pに書き込みます。ReadAtの重要な特性は、ストリームの現在位置を変更しないことです。これにより、複数のゴルーチンが同時に異なるオフセットからデータを読み取ることが可能になり、並行処理において非常に有用です。ファイルやメモリ上のデータなど、ランダムアクセスが可能なデータソースに対して実装されます。
bytes.Readerとstrings.Reader
Goの標準ライブラリには、バイトスライスや文字列をio.Readerインターフェースとして扱うための具体的な実装が提供されています。
bytes.Reader:[]byte(バイトスライス)を読み取り可能なストリームとして扱います。strings.Reader:string(文字列)を読み取り可能なストリームとして扱います。
これらのReader型は、内部に読み取り位置や、場合によっては直前に読み込んだルーン(Unicodeコードポイント)に関する情報(prevRuneなど)を保持しています。
データ競合 (Data Race)
データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって順序付けされていない場合に発生するプログラミング上のバグです。データ競合が発生すると、プログラムの動作が予測不能になったり、クラッシュしたりする可能性があります。
ReadAtメソッドは、その性質上、並行して呼び出されることが想定されるため、内部状態をミューテート(変更)するとデータ競合のリスクが高まります。
r.prevRune = -1 の意味
r.prevRuneは、bytes.Readerやstrings.Readerが内部的に保持するフィールドで、UnreadRuneメソッド(直前に読み込んだルーンをストリームに戻す)をサポートするために、直前に読み込んだルーンの情報を格納します。-1は、有効なルーンが読み込まれていない状態を示すことが多いです。
ReadAtメソッド内でr.prevRune = -1と設定することは、ReadAtが呼び出された際に、UnreadRuneの内部状態をリセットすることを意味します。しかし、前述の通り、ReadAtはストリームの現在位置やその他の内部状態を変更すべきではないため、この操作はReadAtのセマンティクスに反し、並行処理における問題を引き起こす可能性がありました。
技術的詳細
このコミットの技術的な核心は、io.ReaderAtインターフェースのセマンティクスを厳密に遵守することにあります。io.ReaderAtのドキュメントには、「ReadAtは、基礎となる入力ストリームのオフセットを変更しない」と明記されています。この「オフセット」は、単に読み取り位置だけでなく、Readerが持つ他の内部状態(例えばprevRune)にも適用されるべきであるという解釈が、この修正の根底にあります。
CL 77580046では、bytes.Readerとstrings.ReaderのReadAtメソッドに以下の行が追加されました。
r.prevRune = -1
この変更は、ReadAtが呼び出された際に、ReaderのprevRuneフィールドを-1にリセットすることを意味します。prevRuneは、UnreadRuneメソッドが正しく機能するために、直前に読み込まれたルーンの情報を保持する内部状態です。
しかし、ReadAtはランダムアクセスを目的としたメソッドであり、ストリームの現在位置やUnreadRuneの状態のような内部状態を変更すべきではありません。もし複数のゴルーチンが同時にReadAtを呼び出し、その中でr.prevRuneが変更されると、UnreadRuneを使用する他の操作(例えば、同じReaderインスタンスに対してReadやUnreadRuneを呼び出すゴルーチン)との間でデータ競合が発生する可能性がありました。特に、ReadAtがprevRuneをリセットすることで、UnreadRuneが期待する値を見つけられなくなり、テストが失敗する原因となりました。
このコミットでは、CL 77580046で追加されたr.prevRune = -1の行を削除することで、ReadAtメソッドがReaderの内部状態を一切変更しないように修正しています。これにより、ReadAtの呼び出しが副作用を持たなくなり、並行処理環境下での安全性が確保されます。
また、テストファイル(reader_test.goとstrings_test.go)からReadAtに関連するテストケースが削除されているのは、おそらくUnreadRuneErrorTestsというテストスイートが、ReadAtがprevRuneをリセットするという誤った前提に基づいて設計されていたためと考えられます。ReadAtがprevRuneをリセットしないように修正されたため、その前提に基づくテストは不要になったか、あるいは不適切になったため削除されたと推測されます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、以下の2つのファイルにおけるReadAtメソッドからの1行の削除です。
-
src/pkg/bytes/reader.go--- a/src/pkg/bytes/reader.go +++ b/src/pkg/bytes/reader.go @@ -43,7 +43,6 @@ func (r *Reader) Read(b []byte) (n int, err error) { } func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) { - r.prevRune = -1 if off < 0 { return 0, errors.New("bytes: invalid offset") } -
src/pkg/strings/reader.go--- a/src/pkg/strings/reader.go +++ b/src/pkg/strings/reader.go @@ -42,7 +42,6 @@ func (r *Reader) Read(b []byte) (n int, err error) { } func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) { - r.prevRune = -1 if off < 0 { return 0, errors.New("strings: invalid offset") }
また、関連するテストファイルからもReadAtに関するテストケースが削除されています。
-
src/pkg/bytes/reader_test.go--- a/src/pkg/bytes/reader_test.go +++ b/src/pkg/bytes/reader_test.go @@ -138,7 +138,6 @@ var UnreadRuneErrorTests = []struct { f func(*Reader) }{ {"Read", func(r *Reader) { r.Read([]byte{}) }}, - {"ReadAt", func(r *Reader) { r.ReadAt([]byte{}, 0) }}, {"ReadByte", func(r *Reader) { r.ReadByte() }}, {"UnreadRune", func(r *Reader) { r.UnreadRune() }}, {"Seek", func(r *Reader) { r.Seek(0, 1) }}, -
src/pkg/strings/strings_test.go--- a/src/pkg/strings/strings_test.go +++ b/src/pkg/strings/strings_test.go @@ -863,7 +863,6 @@ var UnreadRuneErrorTests = []struct { f func(*Reader) }{ {"Read", func(r *Reader) { r.Read([]byte{}) }}, - {"ReadAt", func(r *Reader) { r.ReadAt([]byte{}, 0) }}, {"ReadByte", func(r *Reader) { r.ReadByte() }}, {"UnreadRune", func(r *Reader) { r.UnreadRune() }}, {"Seek", func(r *Reader) { r.Seek(0, 1) }},
コアとなるコードの解説
このコミットの核心は、ReadAtメソッドが「レシーバをミューテートしない」というio.ReaderAtインターフェースの重要な契約を再確立することです。
bytes.Readerとstrings.ReaderのReadAtメソッドからr.prevRune = -1という行が削除されたことで、ReadAtの呼び出しはReaderインスタンスのprevRuneフィールドに影響を与えなくなりました。これにより、以下の重要な点が保証されます。
- 副作用の排除:
ReadAtは、データの読み取り以外の副作用(この場合は内部状態の変更)を持たなくなります。これは、関数型プログラミングの原則にも通じる、より予測可能で安全なコードの実現に貢献します。 - 並行処理の安全性向上: 複数のゴルーチンが同じ
Readerインスタンスに対して同時にReadAtを呼び出しても、prevRuneフィールドを巡るデータ競合が発生しなくなります。ReadAtはオフセット指定によるランダムアクセスを意図しているため、並行アクセスされることが多く、この変更は並行処理の堅牢性を高めます。 io.ReaderAtセマンティクスの遵守:io.ReaderAtインターフェースのドキュメントに明記されている「基礎となる入力ストリームのオフセットを変更しない」という契約を、より広範な意味で遵守することになります。これは、単に読み取り位置だけでなく、UnreadRuneのような他の操作に影響を与える可能性のある内部状態も変更しないことを意味します。
テストファイルからのReadAt関連のテストケースの削除は、ReadAtがprevRuneをリセットするという誤った前提に基づいていたテストが、この修正によって不要になったか、あるいは不適切になったためと考えられます。ReadAtがprevRuneをリセットしないという正しい挙動になったため、その挙動を期待するテストはもはや意味をなさなくなった、と解釈できます。
この修正は、Go言語の標準ライブラリが提供するインターフェースのセマンティクスを厳密に守り、並行処理における潜在的なバグを未然に防ぐための重要なステップと言えます。
関連リンク
- Go言語の
ioパッケージドキュメント: https://pkg.go.dev/io - Go言語の
bytesパッケージドキュメント: https://pkg.go.dev/bytes - Go言語の
stringsパッケージドキュメント: https://pkg.go.dev/strings - Go言語におけるデータ競合の検出 (Go Race Detector): https://go.dev/doc/articles/race_detector
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
- Go言語の
io.ReaderAtインターフェースに関する一般的な解説 - データ競合に関する一般的なプログラミングの知識
- Go言語のコードレビューシステム (Gerrit) のCL (Change List) の概念