Menu
Coddy logo textTech

Panic、Defer、Recover

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 57/107。

Goはエラーを返すことを重視していますが、中には本当に回復不可能な状況もあります。このようなケースのために、Goはpanicdefer、および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は状態の破損や回復不能な状況などの真に例外的な状況のために予約してください。

challenge icon

チャレンジ

簡単

panicdefer、および 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 された順序とは逆の順序で表示される必要があることに注意してください。

例えば、204 が与えられた場合、出力は以下のようになります:

Starting calculation
Result: 5
Releasing resources
Cleanup complete

また、100 が与えられた場合、出力は以下のようになります:

Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup complete

チートシート

Goは、例外的な状況を処理するためにpanicdefer、および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(&dividend)
	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]」のいずれかを表示する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン