Goでのエラー処理:延期、パニック、および回復

Goは、実行の流れを制御するために、if、for、switch、gotoなどの通常の方法を使用します。 別のthプロシージャでコードを実行するgoステートメントもあります。 次に、あまり一般的ではない方法、つまり、延期、パニック、および回復について説明します。

deferコマンドは、関数呼び出しをリストに入れます。 この保留中の呼び出しリストは、囲んでいる関数が完了した後に実行されます。 通常、遅延は、リソースのリリースを処理する機能を簡素化するために使用されます。

たとえば、2つのファイルを開き、1つのファイルから別のファイルにコンテンツをコピーする関数を見てください。


func CopyFile(dstName、srcName string )(written int64 、err os.Error ){
src、err:= os.Open(srcName、os.O_RDONLY、 0
if err!= nil {
帰る
}

dst、err:= os.Open(dstName、os.O_WRONLY | os.O_CREATE、0644)
if err!= nil {
帰る
}

書かれた、err = io.Copy(dst、src)
dst.Close()
src.Close()
帰る
}


コードは機能していますが、エラーが含まれています。 os.Openの2番目の呼び出しが失敗すると、関数は終了し、最初のファイルは開いたままになります。 これは、2番目の戻り値の前にsrc.Close()の呼び出しを追加することで簡単に修正できますが、関数がより複雑な場合、この問題を見逃す可能性があります。 deferコマンドを入力することにより、任意の条件下でファイルを閉じることができます。

func CopyFile(dstName、srcName string )(written int64 、err os.Error ){
src、err:= os.Open(srcName、os.O_RDONLY、 0
if err!= nil {
帰る
}
src.Close()を延期する

dst、err:= os.Open(dstName、os.O_WRONLY | os.O_CREATE、0644)
if err!= nil {
帰る
}
延期 dst.Close()

return io.Copy(dst、src)
}


deferコマンドを使用すると、関数の終了点の数に関係なく、ファイルを開いた直後にファイルを閉じることを検討できます。

deferコマンドの動作は単純で予測可能です。 3つの簡単なルールがあります。

1. 遅延関数呼び出し引数は、deferコマンドが評価されるときに評価されます。

この例では、Printlnの呼び出しが延期されると、式「i」が評価されます。 遅延呼び出しは、関数から戻った後に「0」を出力します。

func a(){
i:= 0
fmt.Printlnの延期 (i)
i ++
帰る
}


2. 遅延関数呼び出しはLIFOの順序で実行されます最後の遅延呼び出しが最初に呼び出されます-囲んでいる関数の実行が終了した後。

この関数は「3210」を出力します:

func b(){
for i:= 0 ; i < 4 ; i ++ {
fmt.Printの延期 (i)
}
}


3. 遅延関数は、囲んでいる関数の名前付き戻り値を読み取って設定できます。

この例では、遅延関数は、囲んでいる関数が完了した後にiの戻り値をインクリメントします。 したがって、この関数は2を返します。

func c()(i int ){
延期 func (){i ++}()
1を 返す
}


これは、関数によって返されるエラーコードを変更する便利な方法です。 すぐにこの例が表示されます。

パニックは、通常の制御フローを停止してパニックを開始する組み込み関数です。 関数Fがパニックを呼び出すと、Fの実行が停止し、Fで保留中のすべての呼び出しが正常に実行され、Fは呼び出し元の関数に制御を返します。 呼び出し関数では、Fへの呼び出しはパニック呼び出しのように動作します。 プロセスは、現在のthプロシージャのすべての関数の実行が完了するまでスタックを上に進み、その後プログラムがクラッシュします。 パニックは、パニックへの直接の呼び出しと、アレイの境界外へのアクセスなどのランタイムエラーが原因で発生する可能性があります。

Recoverは、パニック状態のgoプロシージャの制御を取り戻す組み込み関数です。 Recoverは、遅延関数呼び出し内でのみ役立ちます。 通常の実行中、recoverはnilを返し、他の効果はありません。 現在のthプロシージャがパニックした場合、recoverの呼び出しは、パニックに渡された値を返し、通常の実行を復元します。

以下に、パニックと延期のメカニズムを示すサンプルプログラムを示します。

パッケージメイン

「fmt」を インポート

func main(){
f()
fmt.Println( "fから正常に返されました。"
}

func f(){
延期 func (){
if r:= recover (); r!= nil {
fmt.Println( "fで回復" 、r)
}
}()
fmt.Println( "Calling g。"
g( 0
fmt.Println( "gから正常に返されました。"
}

func g(i int ){
i> 3の 場合 {
fmt.Println( "パニック!"
パニック (fmt.Sprintf( "%v" 、i))
}
defer fmt.Println( "Defer in g" 、i)
fmt.Println( "gで印刷" 、i)
g(i + 1
}


関数gは、整数iを入力として受け取り、iが3より大きい場合、または引数i + 1との契約自体でパニックを起こします。 f関数は、recoverを呼び出す関数を延期し、復元された値を出力します(値が空でない場合)。 さらに読む前に、このプログラムが何を出力するか想像してみてください。

プログラムは次を出力します。
 gを呼び出す
 g 0での印刷
 G 1での印刷
 G 2での印刷
 G 3での印刷
パニック!
延期3
 g 2で延期
 g 1で延期
 g 0で延期
 f 4で回復
 fから正常に返されました。 


遅延関数呼び出しをfから削除すると、パニックは停止せず、go-procedureの呼び出しスタックの最上部に到達し、プログラムが停止します。 したがって、変更されたプログラムは次を出力します。

 gを呼び出す
 g 0での印刷
 G 1での印刷
 G 2での印刷
 G 3での印刷
パニック!
 g 3で延期
 g 2で延期
 g 1で延期
 g 0で延期
パニック:4
 
パニックPC = 0x2a9cd8
 [コールダウン]


パニックリカバリの実際の使用例については、Go標準ライブラリのjsonパッケージを参照してください。 再帰関数のセットを使用してJSONエンコードデータをデコードします。 誤った形式のJSONが入力に到着すると、パーサーはパニックを呼び出してスタックをトップコールに展開します。これはパニック後に復元され、適切なエラーコードを返します( decode.goの「error」および「 unmarshal関数参照 )。 この手法の同様の例は、 regexpパッケージのコンパイル手順です。 Goライブラリでは、パッケージが内部でパニックを使用している場合でも、その外部APIが明示的なエラーコードを返すという規則があります。

deferの他の用途(上記のfile.Close()の例以外)には、mutexのリリースが含まれます。

mu.Lock()
mu.Unlock()の延期

フッター印刷:

printHeader()
printFooter()を延期する


などなど。

要約すると、deferコマンド(パニックと回復の有無にかかわらず)は、実行の流れを制御するための異常で強力なメカニズムを提供します。 他のプログラミング言語の特別な構造が担当するさまざまな機能をシミュレートするために使用できます。 試してみてください。

PS誤字やその他の誤りに関するコメントは、個人的なメッセージでお知らせください。理由はさまざまな理由から、夕方にしか修正できないからです。

Source: https://habr.com/ru/post/J118898/


All Articles