PR

Go言語(Golang)におけるpanicについて

2. 基礎

こんにちは。Tomoyuki(@tomoyuki65)です。

Go言語(Golang)のサンプルコードでエラーハンドリングがあった際に、よく「panic」関数が使われたりしますが、実務におけるAPI開発ではほぼ使わないです。

この記事では、そんなGo言語(Golang)におけるpanicについてまとめます。

 

Go言語(Golang)におけるpanicについて

Go言語(Golang)のpanic関数とは、プログラムの通常の処理を中断し、異常終了状態に入るための仕組みです。

回復不能なエラーが発生した際に使われるものですが、基本的な役割としては以下の通りです。

・重大なエラーが起きたことを示す
・現在の処理を即座に中断する
・スタックトレースを出力してプログラムを終了する(recover処理が無ければ)

 

実務におけるAPI開発のエラーハンドリング時はどうする?

上記の通り、panic関数は回復不能なエラーが発生した際に使われるものであり、panic関数を使うとサーバーが止まってしまうため、実務におけるAPI開発で使うことはほぼありません。

そのため、実務におけるAPI開発のエラーハンドリング時については、エラー系のレスポンス結果を返すようにするのが基本になります。

※エラー系のレスポンス結果の例:ステータスコードに400番台 or 500番台、レスポンスボディにJSON形式の「{ “error”: “エラーメッセージ” }」など。

 

panic関数を使う場面はある?

上記の通り、実務においてはほぼpanic関数は使いませんが、もし使う場面があるとすれば、絶対に起きてはいけない状態が発生した時になります。
例えばサーバーを起動させるメイン処理(main.go)において、何らかの初期化処理を実行した際に、その初期化処理に失敗したらそもそも後続処理が続けられないというような場面です。

 

・サーバー起動用のメイン処理(main.go)の例
package main

import (
    "fmt"
)

func main() {
    // 何らかのコンフィング設定の初期化処理を実行
    config, err := InitConfig()
    if err != nil {
        // 後続処理で必須のため、panicで終了させる。
        panic(fmt.Errorf("コンフィグ設定の初期化に失敗しました。: %w", err))
    }


    ・・・ configの利用が必須な後続処理 ・・・
}

 

それ以外で使う場面があるとすれば、デバッグ時にpanicさせて処理を止めたい時に使うぐらいかなと思います。

 

逆にpanicを捉えてリカバリー処理をしたい場合はrecover関数を使う

逆にpanicが発生しても何らかのリカバリー処理を実行させたい場合は、「recover」関数を利用することで、同一関数内で発生したpanicを捉えてリカバリー処理をさせることが可能(ただし、ゴルーチンの関数は除く)です。

 

・panicを捉えてリカバリー処理をさせる例

package main

import (
    "fmt"
)

// 関数f
func f() {
    // 即時関数をdeferで実行
    defer func() {
        // 関数f内でpanicが発生するとrを取得してnil以外になりエラー処理を実行する
        if r := recover(); r != nil {
            // 何らかのリカバリー処理を実行
        }
    }()

    // 何らかの処理を実行
}

func main() {
    // 関数fを実行
    f()
}

 

ただし、同一関数内で実行したゴルーチン内で発生したpanicは捉えられないので、その場合はゴルーチン内でリカバリー処理の記述が必要です。

package main

import (
    "fmt"
)

// 関数f
func f() {
    // ※ゴルーチン内で発生したpanicは捉えられないので注意!
    defer func() {
        if r := recover(); r != nil {
            // 何らかのリカバリー処理を実行
        }
    }()

    // ゴルーチンを実行する場合
    go func() {
        // ゴルーチンを使う場合は最初にリカバリー処理を記述する
        defer func() {
            if r := recover(); r != nil {
                // 何らかのリカバリー処理を実行
            }
        }()

        // ゴルーチン内の何らかの処理を実行
    }()
}

func main() {
    // 関数fを実行
    f()
}

※ただし、ゴルーチン内でリカバリー処理を記述しても、panicが起きたゴルーチンはリカバリー処理後に終了するので注意

 

recover関数の乱用は危険なので注意

上記のようにrecover関数を利用すればpanicを捉えてリカバリー処理を実行できますが、以下のようにリカバリーすべきでないpanicもあるため、その点は注意しましょう。

・OOM(メモリを確保しようとしたけど枯渇していた場合)
・runtimeの致命的なエラー(Goの内部整合性が壊れた場合)
・処理を止めるべき設計としているpanic

 

スポンサーリンク

最後に

Go言語(Golang)におけるpanicについてまとめました。

実務のAPI開発においてはpanic関数を使うことはほぼありませんが、逆にrecover関数を使ってリカバリー処理を記述する可能性はあるので、panicやrecoverに関する理解もしておくのは大事です。

 

この記事を書いた人
Tomoyuki

SE→ブロガーを経て、現在はSoftware Engineer(Web/Gopher)をしています!

Tomoyukiをフォローする
2. 基礎
スポンサーリンク
Tomoyukiをフォローする

コメント

タイトルとURLをコピーしました