プログラミング言語

エラーハンドリングとは?例外処理のベストプラクティスとtry-catch-finallyの言語別の違い

エラーハンドリングとは?例外処理のベストプラクティスとtry-catch-finallyの言語別の違い

エラーハンドリングとは、プログラムの実行中に起きた異常を検知し、そのあとプログラムをどう振る舞わせるかを決めて実装することです。異常を伝える手段は例外(exception)だけではなく、戻り値でエラーを返す方法も、入力を事前に検証して異常そのものを起こさせない方法も含まれます。

この記事では、エラーハンドリングと例外処理の違いを整理したうえで、try・catch・finallyの実行順序と言語ごとの差、どの言語にも通じる例外処理のベストプラクティスを解説します。後半ではC#・JavaScript・Python・PowerShell・Laravelそれぞれの公式ドキュメントが推奨する書き方を、コード例と仕様上の動作とあわせて紹介します。

まとめ:例外処理で最初に決める4つのこと

  • どこでcatchするか:回復できない場所ではcatchせず、呼び出し元へ伝播させます。捕捉するのは、リトライ・代替値・ユーザーへの通知など打つ手がある層か、アプリの境界(リクエスト単位・ジョブ単位)です。
  • 原因をどう残すか:再送出するときは元の例外を捨てません。C#なら throw;、Pythonなら raise ... from e、JavaScriptなら new Error(msg, { cause: err }) で元の例外を連結します。
  • 例外にしない失敗はどれか:日常的に起きる失敗は、言語やAPIの慣習に合わせて扱います。C#の数値変換ではTryParseを選べますが、PythonではValueErrorなどを捕捉する書き方も用いられます。
  • 後始末をどこに書くか:リソースの解放はfinallyよりも、C#の using、Pythonの with、Javaのtry-with-resourcesに任せます。finallyの中でreturnすると本来の例外が消える言語があるため、finallyには解放とログだけを書きます。

エラーハンドリングと例外処理の意味と違い

エラーハンドリング(英語で error handling)は、異常への対処の設計と実装の全体を指す言葉です。例外処理(exception handling)はそのうち、言語に用意された例外機構で「エラーを投げて、どこかで捕まえる」方式を指します。IT用語辞典e-Wordsは「エラーを含む、より一般的な概念として『例外』(exception)を用いる言語では、ほぼ同様の機構のことを『例外処理』(exception handling)という」と説明しており、日本語の現場でも両者はほぼ同義で使われます。

エラー・例外・ハンドラーの用語整理

エラーは異常な状態、例外はその通知手段、ハンドラーは通知を受けて処理する関数やブロックを指します。

用語 英語 意味
エラー error 期待どおりに処理できなかった状態の総称
例外(エクセプション) exception 処理の流れを中断して通知される異常
例外処理 exception handling 例外を投げて捕まえる仕組みと実装
エラーハンドラー error handler 異常を受け取って処理する関数やブロック
未処理例外 unhandled exception どこにも捕捉されなかった例外

ハンドラー(handler)は「扱う(handle)もの」の意味で、catchブロックのほか、Laravelの例外ハンドラーやNode.jsの process.on('uncaughtException', ...) に登録する関数もハンドラーと呼ばれます。

例外を使わない言語との違い(Go・Rust)

すべての言語が例外を主役にしているわけではありません。Goは関数の最後の戻り値で error を返し、呼び出し側が if err != nil で分岐します。Rustは Result<T, E> 型で成功と失敗を型として表し、? 演算子で呼び出し元へ伝播させます。どちらも回復不能な事態には panic を別に用意しており、通常の失敗を戻り値で扱う設計を採っていますが、失敗を戻り値とpanicのどちらで通知するかまで言語仕様が強制するわけではありません。Goの書き方はGo言語のエラーハンドリング実装ガイド、例外を使う言語でResult型の考え方を取り入れる方法は鉄道指向プログラミングとはで解説しています。

try・catch・finallyの実行順序と言語別の書き方

try・catch・finallyは、次の順に実行されます。

  1. tryブロックを上から実行する。
  2. 途中で例外が発生すると、try内の残りの行は実行されず、対応するcatchへ移る。C#やJavaでは型などの条件で捕捉先を選ぶが、JavaScriptでは単一のcatchが投げられた値を受け取る。一致するcatchが無ければ例外は呼び出し元へ伝播する。
  3. 例外の有無にかかわらず、tryまたはcatchを抜けるときにfinallyが実行される。tryやcatchの中の return で抜ける場合も同じ。

C#やJavaで型別のcatchを並べる場合は、派生クラス(具体的な例外)を先に、基底クラス(Exception など)を後に書きます。.NETの公式ドキュメントも「catchブロックでは常に、最も派生した例外から最も派生していない例外の順に並べる」よう求めています。

C#・Java・Python・JavaScript・PowerShellの例外処理構文比較

言語 捕捉 送出 そのまま再送出 原因の連結 自動の後始末
C# catch (T e) when (...) throw throw; InnerException using
Java catch (T e) throw throw e; new X(msg, e) try-with-resources
Python except T as e: raise raise raise ... from e with
JavaScript catch (e) throw throw e; { cause: e } using(Node.js 26で動作)
PowerShell catch [T] throw throw InnerException なし(finally)

Pythonは try に else 節を付けられ、「例外が出なかったときだけ実行する処理」をtryの外へ出せます。tryの範囲を狭く保つための構文です。

import json

def load_config(path):
    try:
        f = open(path, encoding="utf-8")
    except FileNotFoundError:
        return {}                 # 設定ファイルが無ければ既定値
    else:
        with f:
            return json.load(f)   # 読めたときだけ解析する
    finally:
        print("load_config 終了")

finallyでreturnすると例外が消える問題

finallyは通常の制御移動や例外伝播の際に後始末を行う場所ですが、プロセスの強制終了などでは実行されない場合があります。また、そこに return を書くと、tryで発生した例外が捨てられ、呼び出し元には何事もなかったように値が返ります。次のコードはtryで例外を投げていますが、関数は例外を出さずに終了します。

# Python
def f():
    try:
        raise ValueError("本来の例外")
    finally:
        return "finallyの戻り値"

print(f())

同じ形のコードを各処理系で動かしたときの挙動は次のとおりです。

処理系 結果 警告
Python 3.13以前 「finallyの戻り値」を出力 なし
CPython 3.14 「finallyの戻り値」を出力 SyntaxWarning
Node.js 「finallyの戻り値」を出力 なし
Java(javac -Xlint) 「finallyの戻り値」を出力 finally clause cannot complete normally
C# コンパイルエラー CS0157

CPython 3.14はPEP 765により、finallyの外へ制御を移す return・break・continue に SyntaxWarning: 'return' in a 'finally' block を出すようになりました。Javaは -Xlint:finally(-Xlint:all に含まれる)を付けたときだけ警告します。C#は言語仕様でfinallyから制御を抜けることを禁じており、コンパイラが「コントロールが finally 句の本体から出られません」(CS0157)で止めます。

避け方は単純で、finallyにはリソースの解放とログだけを書き、戻り値はtryかcatchで決めます。finallyの中で例外を投げるのも同じ理由で避けます。.NETの推奨事項にも「finally句で例外を発生させない」(コード分析規則CA2219)が挙がっています。

例外処理のベストプラクティス(言語共通)

言語が違っても、例外処理で押さえることはほぼ共通しています。ここでは.NETの公式ガイド「例外のベスト プラクティス」を軸に、他言語でも通用する原則を6つに絞りました。

回復可能性に基づくcatchの配置

.NETのガイドは「コードが例外から回復できない場合、その例外をキャッチしない。可能であれば呼び出し履歴の上位のメソッドで回復できるようにする」と書いています。各関数でcatchしてログを出すだけで握りつぶすと、呼び出し元が失敗を知らないまま処理を続け、未更新の値や不完全なデータを保存するおそれがあります。catchを置くのは、リトライ・代替値・トランザクションの取り消しなど打つ手がある場所と、Webリクエストやバッチジョブの単位でまとめて記録する境界の2種類に絞ります。

空のcatchによる障害情報の消失

catch (Exception) {} のような空のcatchは、障害の痕跡を消す最悪のパターンです。どうしても続行する必要があるなら、少なくとも例外オブジェクトごとログに残し、なぜ続行してよいかをコメントに書きます。例外を広く捕まえる catch (Exception e) も、境界のハンドラー以外では使わず、具体的な型を指定します。

再送出時のスタックトレースと原因例外の保持

C#で throw ex; と書くと、スタックトレースが現在のメソッドから始まり直し、例外が実際に起きた場所の情報が失われます。.NETのガイドは、catchした場所で再送出するなら引数なしの throw;、別の場所で投げ直すなら ExceptionDispatchInfo.Capture と Throw を使うよう求めています(コード分析規則CA2200が throw ex; を検出します)。業務的な意味のある例外に包み直す場合は、元の例外を内部例外として渡します。

# Python:raise ... from で原因を連結する
class OrderError(Exception):
    pass

def load(order_id):
    try:
        int(order_id)
    except ValueError as e:
        raise OrderError(f"注文ID {order_id!r} を読めません") from e

try:
    load("A-1")
except OrderError as e:
    print(type(e).__name__, e)
    print("cause:", type(e.__cause__).__name__, e.__cause__)

実行すると OrderError 注文ID 'A-1' を読めません に続いて cause: ValueError invalid literal for int() with base 10: 'A-1' が出力され、元の例外が __cause__ に残っていることが確認できます。

想定内の失敗と例外処理の言語別の使い分け

.NETのガイドは、頻繁に起きる状況はif文などで事前に確認し、本当に例外的な事態だけを例外で扱うよう勧めています。C#で数値変換や辞書検索の失敗が頻発する場合は、TryParseやTryGetValueで例外の送出・捕捉を通常の分岐に置き換えられます。int.Parse は変換できない値で例外を投げますが、int.TryParse は bool を返すだけで例外を投げません。Pythonは逆に「やってみて失敗したら例外で扱う(EAFP)」が慣習ですが、入力検証でも、intによる変換で発生するValueErrorを捕捉する書き方が公式チュートリアルに示されています。利用者へ返す検証結果と、内部で例外を使うかどうかは分けて考えます。

using・with・try-with-resourcesによる後始末

ファイルやDB接続の解放をfinallyに手書きすると、解放そのものが失敗したときに本来の例外が上書きされます。Javaのtry-with-resourcesは、本処理の例外を優先して投げ、close時の例外を「抑制された例外」として添付します。

public class Twr {
    static class Res implements AutoCloseable {
        public void close() { throw new IllegalStateException("close失敗"); }
    }
    public static void main(String[] args) {
        try (Res r = new Res()) {
            throw new RuntimeException("本処理の失敗");
        } catch (RuntimeException e) {
            System.out.println("主: " + e.getMessage());
            for (Throwable s : e.getSuppressed()) {
                System.out.println("抑制: " + s.getMessage());
            }
        }
    }
}

このコードは「主: 本処理の失敗」「抑制: close失敗」の順に出力します。try-with-resourcesでは本処理の例外が優先され、解放時の例外は抑制された例外として取得できます。C#の using とPythonの with も例外発生時の解放を言語側が保証しますが、抑制の規則はJavaと異なります。C#の using は try-finally 相当なので、Dispose が投げた例外が本処理の例外を置き換えることがあります。Pythonの with では __exit__ が送出した例外が伝播し、元の例外は __context__ に残ります。

利用者向け表示と診断ログの分離

スタックトレースやSQL文、ファイルパスを画面やAPIレスポンスにそのまま返すと、攻撃者にシステムの内部構造を教えることになります。利用者には「何が起きたか」と「次に何をすればよいか」だけを伝え、詳細はログ側に残して、両者を問い合わせIDで結び付けます。APIのエラー形式の決め方はAPIエラーレスポンス設計とは、一時的な失敗を自動で再試行する設計はリトライパターンとはで扱っています。

C#の例外処理ベストプラクティス(.NET公式の推奨)

C#で特に効くのは、C# 6で導入された例外フィルター(when)と throw; の使い分けです。例外フィルターはスタックを巻き戻す前に評価されるため、条件に合わない例外はcatchに入らず、元の呼び出し履歴を保ったまま上位へ伝わります。

using System;
using System.IO;
using System.Text.Json;

public sealed record Order(int Id, string Item);

public sealed class OrderFormatException : Exception
{
    public OrderFormatException() { }
    public OrderFormatException(string message) : base(message) { }
    public OrderFormatException(string message, Exception inner) : base(message, inner) { }
}

public static class OrderLoader
{
    public static Order Load(string path)
    {
        try
        {
            var json = File.ReadAllText(path);
            return JsonSerializer.Deserialize<Order>(json)
                ?? throw new OrderFormatException($"注文ファイルが空です: {path}");
        }
        catch (IOException ex) when (ex is not FileNotFoundException)
        {
            Console.Error.WriteLine($"注文ファイルを読めません: {ex.Message}");
            throw;   // スタックトレースを保ったまま再送出
        }
        catch (JsonException ex)
        {
            throw new OrderFormatException($"注文ファイルの形式が不正です: {path}", ex);
        }
    }
}

独自例外クラスは、.NETのガイドどおり「既定の例外型で表せないときだけ」作り、名前を Exception で終え、引数なし・メッセージ・メッセージと内部例外の3つのコンストラクターを用意します。非同期メソッドでは TaskCanceledException ではなく基底の OperationCanceledException をcatchし、キャンセルを確認する側は CancellationToken.ThrowIfCancellationRequested を呼ぶのが推奨です。例外を使わずに失敗を戻り値で表したい場合は、FluentResultsとはで紹介しているResult型のライブラリが選択肢になります。

JavaScript・Pythonの例外処理で押さえる差分

JavaScript:throw new Errorとcauseの連結

JavaScriptは文字列でも数値でも throw できますが、一般的な実装では Error オブジェクトにスタックトレースが付くため、必ず throw new Error(...) の形で投げます。ES2022で Error のコンストラクターに cause オプションが入り、元の例外を連結できるようになりました(Node.jsは16.9.0以降)。

function load(orderId) {
  try {
    JSON.parse(orderId);
  } catch (err) {
    throw new Error(`注文データ ${orderId} を読めません`, { cause: err });
  }
}

try {
  load("{broken");
} catch (err) {
  console.log(err.message);          // 注文データ {broken を読めません
  console.log("cause:", err.cause.name); // cause: SyntaxError
}

catchに入った値は Error とは限らないため、TypeScriptでは catch (err) の err が unknown 型になる設定(useUnknownInCatchVariables)を前提に、err instanceof Error で絞り込んでから使います。async/await とPromiseの例外処理、Promise.all と Promise.allSettled の使い分けはJavaScriptの非同期エラーハンドリングと並列実行にまとめています。

Python:exceptの範囲とExceptionGroup

Pythonのtryでは、裸の except: や except BaseException: を避けます。KeyboardInterrupt や SystemExit まで捕まえてしまい、Ctrl+Cでプログラムを止められなくなるためです。広く捕まえるとしても except Exception: までに留めます。Python 3.11からは、並行処理で複数の例外がまとめて届く ExceptionGroup と、それを型ごとに振り分ける except* 構文が使えます。

PowerShellの非終了エラーとcatchの捕捉条件

PowerShellのエラーには、処理を止める「終了エラー」と、エラーを表示して次の処理へ進む「非終了エラー」があります。try/catchが捕まえるのは終了エラーだけで、Microsoftのドキュメント「about_Error_Handling」も「非終了エラーは既定では catch や trap をトリガーしない」と明記しています。存在しないファイルに Get-Content を実行しても、catchには入りません。

try {
    Get-Content ./no-such-file.txt
    Write-Output "catchされずに次の行へ進んだ"
} catch {
    Write-Output "catch: $($_.Exception.Message)"
}

try {
    Get-Content ./no-such-file.txt -ErrorAction Stop
    Write-Output "ここは実行されない"
} catch [System.Management.Automation.ItemNotFoundException] {
    Write-Output "catch: $($_.Exception.GetType().Name)"
} finally {
    Write-Output "finally: 後始末"
}

PowerShellの既定設定では、1つ目のtryはエラーを表示したうえで「catchされずに次の行へ進んだ」を出力し、2つ目は「catch: ItemNotFoundException」「finally: 後始末」を出力します。コマンドレットに -ErrorAction Stop を付けるか、スクリプトの先頭で $ErrorActionPreference = 'Stop' を設定すると、非終了エラーが終了エラーに昇格してcatchで扱えます。catchの中では、現在のエラーを $_(別名 $PSItem)で参照します。

Laravelの例外処理:bootstrap/app.phpのwithExceptions

Laravel 11以降の新しいアプリケーション構成(2026年9月時点の最新は13.33.0)では、例外ハンドラーの設定を app/Exceptions/Handler.php ではなく bootstrap/app.php の withExceptions に書きます(Laravel 10以前から更新したアプリは旧構成のままでも動作し、公式のアップグレードガイドも構成の移行を必須としていません)。例外の記録(report)と、レスポンスへの変換(render)を分けて設定するのがLaravelの考え方です。次の例は bootstrap/app.php の全体で、withMiddleware などの設定は省略しています。

<?php

use App\Exceptions\InvalidOrderException;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Http\Request;
use Psr\Log\LogLevel;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
    )
    ->withExceptions(function (Exceptions $exceptions): void {
        // 記録:特定の例外だけ外部サービスへ送る
        $exceptions->report(function (InvalidOrderException $e) {
            // ...
        });

        // ログレベルを例外の種類ごとに変える
        $exceptions->level(PDOException::class, LogLevel::CRITICAL);

        // 表示:APIではJSONで返す
        $exceptions->render(function (InvalidOrderException $e, Request $request) {
            if ($request->is('api/*')) {
                return response()->json(['message' => '注文内容を確認してください'], 422);
            }
        });
    })
    ->create();

report に登録したクロージャーが動いても、Laravelは既定のログ設定でも例外を記録します。二重に記録したくない場合は ->stop() を付けるか、クロージャーから false を返します。記録したくない例外は dontReport に渡すか、例外クラスに ShouldntReport インターフェースを実装します。本番環境で APP_DEBUG を true にすると、例外画面に設定値が表示されるため、Laravelの公式ドキュメントも本番では必ず false にするよう警告しています。

ハンドルされていない例外(未処理例外)の扱い

どのcatchにも捕まらなかった例外は、多くの処理系でプロセスを終了させます。この状態を.NETでは「ハンドルされていない例外(unhandled exception)」、Javaでは「捕捉されない例外(uncaught exception)」と呼び、Windowsアプリで表示される「ハンドルされていない例外が発生しました」というダイアログもこれにあたります。

各言語には未処理例外の通知先がありますが、対象となるスレッドや呼び出し回数、通知後の終了動作は異なります。

言語 未処理例外の受け皿
.NET AppDomain.CurrentDomain.UnhandledException
Java Thread.setDefaultUncaughtExceptionHandler
Node.js process.on('uncaughtException', ...)
Python sys.excepthook

これらは最後に記録を残すための受け皿で、処理を続行するためのものではありません。Node.jsのドキュメントは 'uncaughtException' を「例外処理のための粗雑な仕組み」と呼び、発生後に通常の処理を再開するのは安全ではないと明記しています。Node.jsのuncaughtExceptionでは同期的な記録とリソースの後始末を行い、プロセスを終了させます。監視側で終了を検知し、再起動を管理します。他言語ではスレッドやアプリケーションモデルごとの終了動作を確認します。

よくある質問

エラーハンドリングは英語で何と書きますか?

error handling です。例外処理は exception handling、例外そのものは exception(カタカナでエクセプション)と書きます。

エクセプションとはどういう意味ですか?

exception(例外)のカタカナ表記で、処理の流れを中断して通知される異常を指します。「例外がスローされました」は、この例外が投げられたという意味のメッセージです。

ハンドラーとは何を指しますか?

発生したエラーやイベントを受け取って処理する関数やブロックのことです。例外処理ではcatchブロックや、未処理例外の受け皿として登録する関数がハンドラーにあたります。

try catchはどんなときに使いますか?

ファイル・ネットワーク・外部API・データベースなど、自分のコードでは防げない失敗が起きうる処理で、その場で打つ手(再試行・代替値・利用者への通知)があるときに使います。打つ手が無ければcatchせず、呼び出し元へ伝播させます。

エラーと例外は何が違いますか?

エラーは処理がうまくいかなかった状態の総称で、例外はそれを言語の例外機構で通知する手段です。Javaのように、回復を想定しない重大な異常を Error、回復を想定するものを Exception とクラスで分ける言語もあります。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2025.01.09 コラム アドレサブルCM(Addressable CM)とは?フジテレビで出る表示の意味・仕組みと視聴データの扱い
  3. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】
  4. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  5. 2025.12.10 コラム 今の50代は何世代?バブル世代・団塊ジュニア・氷河期の境目と特徴

RELATED POSTS 関連記事

目次