Panic、Defer、Recover
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 57/107。
Goはエラーを返すことを重視していますが、中には本当に回復不可能な状況もあります。このようなケースのために、Goはpanic、defer、およびrecoverという、例外的な状況を処理するためのメカニズムを提供しています。
panic は通常の実行を即座に停止します。これは、範囲外のインデックスへのアクセスのようなプログラミングエラーのために予約されており、無効なユーザー入力のような予期されるエラーのためのものではありません:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer は、周囲の関数が(正常終了かパニックによるものかを問わず)リターンしたときに関数を実行するようにスケジュールします。遅延実行される呼び出しは、逆順(後入れ先出し)で実行されます:
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// 出力: Opening file, Processing..., Closing file
}recover はパニックをキャッチし、通常の実行を再開します。これは、遅延実行(deferred)関数内でのみ動作します:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}このパターンはパニックを通常のエラーに変換し、プログラムの実行を継続できるようにします。これの使用は控えめにしてください。予期される失敗ケースにはエラーを返すことを優先し、panic/recoverは状態の破損や回復不能な状況などの真に例外的な状況のために予約してください。
チャレンジ
簡単panic、defer、および recover を使用して、例外的な状況を適切に処理する方法を示す安全な割り算計算機を作成しましょう。この計算機は、リスクのある操作を試行し、パニックを通常通り処理できる標準的なエラーに変換します。
コードは2つのファイルに分けて構成します:
calculator.go: パニックリカバリ機能を備えた割り算ロジックを作成します。2つの整数を受け取り、除数がゼロの場合はメッセージ
"division by zero"でパニックを発生させるDivide関数を実装してください。それ以外の場合は、割り算の整数の結果を返します。2つの整数を受け取り、整数の結果とエラーの両方を返す
SafeDivide関数を実装してください。この関数は以下のことを行う必要があります:deferと無名関数を使用して、あらゆるパニックからリカバリ(回復)する- パニックがリカバリされた場合、それを
"calculation error: [panic message]"という形式のエラーに変換する - 内部で
Divide関数を呼び出す - 成功した場合は結果と
nilを返し、パニックがリカバリされた場合はゼロとエラーを返す
main.go: 入力から2つの整数を読み取り、SafeDivideを使用して安全に計算を実行します。また、クリーンアップメッセージを正しい順序で表示することでdeferの動作を示します。main関数は以下のことを行う必要があります:
- 最初に
Starting calculationを表示する deferを使用してCleanup completeの表示をスケジュールする- 別の
deferを使用してReleasing resourcesの表示をスケジュールする - 入力値を使用して
SafeDivideを呼び出す - 結果に応じて
Result: [value]またはError: [error message]のいずれかを表示する
- 最初に
以下の入力が提供されます:
- 1行目:最初の整数(被除数)
- 2行目:2番目の整数(除数)
遅延呼び出し(deferred calls)は逆順(後入れ先出し:LIFO)で実行されるため、クリーンアップメッセージは defer された順序とは逆の順序で表示される必要があることに注意してください。
例えば、20 と 4 が与えられた場合、出力は以下のようになります:
Starting calculation
Result: 5
Releasing resources
Cleanup completeまた、10 と 0 が与えられた場合、出力は以下のようになります:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeチートシート
Goは、例外的な状況を処理するためにpanic、defer、およびrecoverを提供しています。
panicは通常の実行を即座に停止させ、回復不可能なプログラミングエラーのために予約されています:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}deferは、周囲の関数がリターンする時に実行される関数をスケジュールします。遅延呼び出しは逆順(後入れ先出し)で実行されます:
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// 出力: Opening file, Processing..., Closing file
}recoverはパニックをキャッチし、通常の実行に戻します。これは遅延関数内でのみ機能します:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}このパターンはパニックを通常のエラーに変換します。予期される失敗にはエラーを返すことを優先し、panic/recoverは真に例外的な状況のために予約してください。
自分で試してみよう
package main
import (
"fmt"
)
func main() {
// 入力を読み込む
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// 開始メッセージを表示
fmt.Println("Starting calculation")
// TODO: deferを使用して「Cleanup complete」メッセージをスケジュールする
// TODO: deferを使用して「Releasing resources」メッセージをスケジュールする
// 覚えておくこと:deferで呼び出された関数はLIFO(後入れ先出し)順で実行されます
// TODO: 入力値を使用してSafeDivideを呼び出す
// TODO: エラーが返されたかどうかに基づいて、
// 「Result: [value]」または「Error: [error message]」のいずれかを表示する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー