8.1 エラーの種類

プログラムの失敗は、大きく三種類に分けられます。

プログラミングエラー

存在しないメソッドを呼ぶ
配列の範囲外へアクセスする
型の異なる値を渡す

通常はバグです。

業務上の失敗

ユーザーが見つからない
残高が不足している
在庫がない
認証に失敗した

想定される分岐です。

外部要因による失敗

ネットワーク障害
データベース停止
ファイルが存在しない
タイムアウト

実行環境に依存します。


8.2 例外

多くの言語では、通常の戻り値とは別経路でエラーを伝播させます。

try:
    user = load_user()
except UserNotFoundError:
    print("not found")

例外には、呼び出し階層を自動的にさかのぼれる利点があります。

一方で、関数の型だけを見ても何が発生するか分かりにくい場合があります。


8.3 Result型

Rustでは、失敗を戻り値として表すのが基本です。

enum Result<T, E> {
    Ok(T),
    Err(E),
}
fn load_user(id: u64) -> Result<User, LoadUserError> {
}

呼び出し側は成功と失敗を処理します。

match load_user(1) {
    Ok(user) => println!("{}", user.name),
    Err(error) => eprintln!("{error}"),
}

?演算子で、失敗を呼び出し元へ伝播できます。

fn process() -> Result<(), Error> {
    let user = load_user(1)?;
    save_user(user)?;
    Ok(())
}

8.4 エラーコード

Goでは、通常値とエラーを複数戻り値として返す形が基本です。

user, err := loadUser(1)

if err != nil {
    return err
}

考え方としてはResult型と近く、エラーを通常の制御フローとして明示的に処理します。


8.5 例外とResult型の比較

観点 例外 Result・error
正常系の読みやすさ 高い エラー処理が目に見える
失敗の型表現 弱い場合がある 戻り値型に現れる
自動伝播 しやすい 演算子などで支援
処理漏れ 起きることがある 型検査で検出しやすい
想定外の障害 扱いやすい すべて値として扱う必要がある

どちらが絶対に優れているのではなく、言語の設計思想が異なります。