概要
Goでは、panicの乱用は推奨されないことが多いと思いますが、ループ処理中などに、不意にpanicを吐く外部パッケージが紛れ込んでしまった時など、panicを他のエラーと同様にハンドリングして処理を最後まで続けたいケースがありました。
今回は一つの解決の方針として、panicの発生する部分を無名関数でくるんで捕まえる、という方法を紹介します。
内容
例1:処理をそのままrecoverした場合
package main
import "fmt"
func main() {
var j int
var k int
for i := 1; i < 10; i++ {
// 回数
fmt.Printf("i = %d\n", i)
// panic時にはrecover
defer func() {
if err := recover(); err != nil {
fmt.Println("recover:", err)
}
}()
// panicを吐く可能性がある処理
hoge(i, j)
huga(i, k)
}
}
func hoge(i int, j int) {
return
}
func huga(i int, k int) {
if i == 5 {
panic("panic")
}
return
}
実行結果
i = 1
i = 2
i = 3
i = 4
i = 5
recover: panic
関数内でpanicが発生した時、deferを実行し、元の関数を終了する。
したがって、例1を実行すると i = 5 の時panicが発生し、deferでrecoverされるので正常終了するが、main関数が終了してしまうので、ループ処理も途中で終了してしまう。
例2:処理を無名関数でくるんでrecoverした場合
package main
import "fmt"
func main() {
var j int
var k int
for i := 1; i < 10; i++ {
// 無名関数でラップする
func() {
// 回数
fmt.Printf("i = %d\n", i)
// panic時にはrecover
defer func() {
if err := recover(); err != nil {
fmt.Println("recover:", err)
}
}()
// panicを吐く可能性がある処理
hoge(i, j)
huga(i, k)
}()
}
}
func hoge(i int, j int) {
return
}
func huga(i int, k int) {
if i == 5 {
panic("panic")
}
return
}
実行結果
i = 1
i = 2
i = 3
i = 4
i = 5
recover: panic
i = 6
i = 7
i = 8
i = 9
例2は、ループの中身を無名関数で囲んだだけであるが、defer実施後に終了される関数が、無名関数となり、ループは無名関数の外側にあるので終了しない。(無名関数からローカル変数を参照できるので、処理に必要な引数の引き渡しも考慮する必要がない)
まとめ
Goでは、panicの多用は推奨されないことが多いのですが、そのため、逆に不意にpanicが混じった時にハンドリングできず困ることもあるかもしれません。今回は一つの解決法として、panicの発生する部分を無名関数でくるんで捕まえる、という方法を紹介しました。