導入
「うまくいかないかもしれない処理」をどう扱うかは、どの言語でも重要なテーマです。Go には例外(try/catch)がなく、代わりに error を戻り値として返す スタイルを徹底します。ここでは、これまで見てきた error・defer に加え、「本当に致命的な異常」のための panic を実務の視点でまとめます。
説明
まずは、エラー値と defer を組み合わせた典型的な形です。
package main
import (
"errors"
"fmt"
)
func withdraw(balance, amount int) (int, error) {
if amount > balance {
return balance, errors.New("残高が不足しています")
}
return balance - amount, nil
}
func main() {
defer fmt.Println("取引処理を終了しました")
newBalance, err := withdraw(1000, 1500)
if err != nil {
fmt.Println("エラー:", err)
return
}
fmt.Println("新しい残高:", newBalance)
}
withdrawは「結果」と「エラー」の2つを返し、呼び出し側はif err != nilで必ず確認します。defer fmt.Println(...)は、mainの途中でreturnしても必ず実行されます。「エラーで早期リターンしても後始末は忘れない」という安心感がdeferの価値です。
error は「予期される失敗」(残高不足・入力ミスなど)への対応です。一方 panic は、「プログラムを続けられないほど致命的な異常」が起きたときに使います。
func mustPositive(n int) int {
if n < 0 {
panic("負の数は扱えません") // ここで処理を強制的に打ち切る
}
return n
}
panic が起きると、その時点で関数の実行を中断し、呼び出し元へと巻き戻りながら defer だけは実行して、最終的にプログラムが異常終了します。
開始
後始末
panic: 想定外のエラー
上のように、panic が起きても defer で予約した後始末は実行されてから終了する――この順番を覚えておきましょう。
実際の Go には、defer の中で recover() を呼ぶことで panic を捕まえ、プログラムを止めずに処理を続ける仕組みもあります(if r := recover(); r != nil { ... } という形で書きます)。Web サーバーが1つのリクエスト処理で panic しても、サーバー全体を落とさずに済むのはこの recover のおかげです。ただし、このサイトの学習用シミュレータは recover の実際の動作には対応していないため、実際に試すには手元の Go 環境や Go Playground を使ってください。
使い分けの目安:
| 状況 | 使うもの |
|---|---|
| 入力ミス・残高不足など、起こりうる失敗 | error を返す |
| 後始末(クローズ処理など)を必ず行いたい | defer |
| バグに近い、続行不可能な異常 | panic(呼び出し側のライブラリではまず使わない) |
実務では、error を返せる場面で安易に panic を使わないのが原則です。panic はプログラム全体を止めてしまうため、ライブラリのコードでは特に慎重に使います。
やってみよう
withdraw(1000, 1500) の第2引数を 1000 以下の値に変えると、正常な取引として 新しい残高: ... が表示されます。defer で予約した「取引処理を終了しました」は、成功・失敗どちらの場合でも必ず表示される点を確かめてください。
演習
withdraw(1000, 1500) の呼び出しを withdraw(1000, 400) に変更して実行し、正常に引き出せた場合の残高と、defer によるメッセージの両方が表示されることを確認してください。
ヒント1を見る
newBalance, err := withdraw(1000, 400) のように、呼び出しの第2引数だけを書き換えます。
ヒント2を見る
amount(400) が balance(1000) を超えないので err は nil になり、fmt.Println("新しい残高:", newBalance) が実行されたあと、defer の行が最後に表示されます。