例外に対するGoogleの代替手段は
- GO: 複数値を返す "return val, err;"
- GO、C++: nil チェック (早期リターン)
- GO、C++: 「いまいましいエラーを処理する」(私の用語)
C++: アサート (式)
GO: defer/panic/recover は、この質問の後に追加された言語機能です。
複数の値を返すことは、代替手段として機能するのに十分有用ですか? 「アサート」が代替と見なされるのはなぜですか? 正しく処理されないエラーが発生した場合にプログラムが停止しても、Google は問題ないと考えますか?
Go の珍しい機能の 1 つは、関数とメソッドが複数の値を返すことができることです。これは、C プログラムのいくつかの不器用なイディオムを改善するために使用できます: インバンドエラーリターン (EOF の -1 など) と引数の変更。
C では、書き込みエラーは負のカウントによって通知され、エラー コードは揮発性の場所に隠されます。Go では、Write はカウントとエラーを返すことができます。パッケージ os の *File.Write の署名は次のとおりです。
func (file *File) Write(b []byte) (n int, err Error)
ドキュメントにあるように、n != len(b) の場合、書き込まれたバイト数と nil 以外のエラーが返されます。これは一般的なスタイルです。その他の例については、エラー処理のセクションを参照してください。
Go 関数の戻り値または結果の「パラメーター」には、入力パラメーターと同様に、名前を付けて通常の変数として使用できます。名前が付けられると、関数の開始時にその型のゼロ値に初期化されます。関数が引数なしで return ステートメントを実行する場合、結果パラメーターの現在の値が戻り値として使用されます。
名前は必須ではありませんが、コードを短く明確にすることができます。名前はドキュメントです。nextInt の結果に名前を付けると、返された int がどれであるかが明らかになります。
func nextInt(b []byte, pos int) (value, nextPos int) {
名前付きの結果は初期化され、飾り気のない戻り値に結び付けられるため、単純化して明確にすることができます。それらをうまく使用する io.ReadFull のバージョンを次に示します。
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
for len(buf) > 0 && err == nil {
var nr int;
nr, err = r.Read(buf);
n += nr;
buf = buf[nr:len(buf)];
}
return;
}
例外も同様の話です。例外に対する多くの設計が提案されていますが、それぞれが言語とランタイムにかなりの複雑さを加えています。その性質上、例外は関数にまたがり、ゴルーチンにまで及ぶ可能性があります。それらは幅広い意味を持ちます。図書館への影響も懸念されます。それらは定義上例外的ですが、それらをサポートする他の言語での経験は、それらがライブラリーとインターフェースの仕様に大きな影響を与えることを示しています。一般的なエラーが、すべてのプログラマーが補償を必要とする特別な制御フローに変わることを助長することなく、それらを真に例外的なものにすることができる設計を見つけることは素晴らしいことです.
ジェネリックと同様に、例外は未解決の問題のままです。
決断:
一見すると、特に新しいプロジェクトでは、例外を使用する利点がコストを上回ります。ただし、既存のコードの場合、例外の導入はすべての依存コードに影響を与えます。例外が新しいプロジェクトを超えて伝播する可能性がある場合、新しいプロジェクトを既存の例外のないコードに統合することも問題になります。Google の既存の C++ コードのほとんどは例外を処理する準備ができていないため、例外を生成する新しいコードを採用することは比較的困難です。
Google の既存のコードは例外に寛容ではないため、例外を使用するコストは、新しいプロジェクトでのコストよりもいくらか高くなります。変換プロセスは遅く、エラーが発生しやすくなります。エラー コードやアサーションなど、例外に代わる利用可能な代替手段が大きな負担をもたらすとは考えていません。
例外の使用に対する私たちのアドバイスは、哲学的または道徳的根拠に基づいているのではなく、実際的な理由に基づいています。Google でオープンソース プロジェクトを使用したいのですが、それらのプロジェクトで例外が使用されている場合は使用が難しいため、Google オープンソース プロジェクトでも例外に対してアドバイスする必要があります。最初からやり直さなければならない場合は、おそらく状況が異なるでしょう。
defer ステートメントを使用すると、各ファイルを開いた直後に閉じることを考えることができ、関数内の return ステートメントの数に関係なく、ファイルが閉じられることが保証されます。
defer ステートメントの動作は単純で予測可能です。3 つの簡単なルールがあります。
1. 遅延ステートメントが評価されるときに、遅延関数の引数が評価されます。
この例では、式 "i" は、Println 呼び出しが延期されるときに評価されます。遅延呼び出しは、関数が戻った後に「0」を出力します。
func a() { i := 0 defer fmt.Println(i) i++ return }
2. 遅延関数呼び出しは、周囲の関数が戻った後、後入れ先出しの順序で実行されます。この関数は「3210」を出力します。
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } }
3. 遅延関数は、返される関数の名前付き戻り値を読み取って割り当てることができます。
この例では、遅延関数は、周囲の関数が戻った後に戻り値 i をインクリメントします。したがって、この関数は 2 を返します。
func c() (i int) { defer func() { i++ }() return 1 }
これは、関数のエラー戻り値を変更するのに便利です。この例については後ほど説明します。
パニックは、通常の制御フローを停止してパニックを開始する組み込み関数です。関数 F が panic を呼び出すと、F の実行が停止し、F 内のすべての遅延関数が正常に実行され、F が呼び出し元に戻ります。呼び出し元にとって、F は panic の呼び出しのように振る舞います。このプロセスは、現在の goroutine 内のすべての関数が返されるまでスタックを上っていき、その時点でプログラムがクラッシュします。パニックは、パニックを直接呼び出すことで開始できます。また、範囲外の配列アクセスなどの実行時エラーによっても発生する可能性があります。
Recover は、パニック状態のゴルーチンの制御を取り戻す組み込み関数です。Recover は、遅延関数内でのみ役立ちます。通常の実行中、recover を呼び出すと nil が返され、それ以外の効果はありません。現在のゴルーチンがパニック状態の場合、recover を呼び出すと、panic に指定された値が取得され、通常の実行が再開されます。
パニックと遅延のメカニズムを示すプログラムの例を次に示します。
<snip>
パニックと回復の実際の例については、Go 標準ライブラリの json パッケージを参照してください。一連の再帰関数を使用して、JSON でエンコードされたデータをデコードします。不正な形式の JSON が検出されると、パーサー呼び出しの panic はスタックを最上位の関数呼び出しに巻き戻し、パニックから回復し、適切なエラー値を返します (decode.go の「error」および「unmarshal」関数を参照してください)。 . regexp パッケージの Compile ルーチンには、この手法の同様の例があります。Go ライブラリの規則では、パッケージが内部で panic を使用している場合でも、その外部 API は明示的なエラー戻り値を提示します。
defer の他の用途 (前述の file.Close() の例を超えて) には、ミューテックスの解放が含まれます。
mu.Lock() defer mu.Unlock