Skip to main content

第12章:例外の基礎(まず“投げる・握る”のルール)💥🚧

例外(Exception)は、うまく使うと 「壊れたときにすぐ直せるコード」 になります✨ でも使い方を間違えると、バグが隠れて大事故になりがち…😱💦 この章では、まず “投げる(throw)・握る(catch)”の基本ルール を、実務で困らない形で身につけます🧠🔧


この章のゴール🎯✨

  • ✅ 「どこで throw するべきか」が分かる
  • ✅ 「どこで catch するべきか」が分かる
  • ✅ “握りつぶし”をやめて、原因が追える例外処理にできる
  • ✅ デバッガで例外を追える(Exception Helper / Exception Settings)🔍
  • ✅ AIの提案を“安全に”使って例外処理を整えられる🤖🛡️

1) 例外って何?🤔💭(超ざっくり)

例外は「処理が続けられない失敗」を伝える仕組みです💥 発生すると、.NET は スタックトレース(どの順で呼ばれたか) を持った状態で上へ上へ伝播します🧗‍♀️⬆️

例外に入っている主な情報📦

  • Message:人間向けの説明📝
  • StackTrace:どこから呼ばれてここに来たかの道順🗺️
  • InnerException:原因になった例外(ラップしたとき)🥟

Visual Studio では、例外が起きた瞬間に Exception Helper が出て、InnerException も追えます👀✨ (Microsoft Learn)


2) “投げる(throw)”の基本ルール🧨

ルールA:例外は「通常ルート」に使わない🚫

「分岐のために例外を使う」は避けるのが原則です🙅‍♀️ たとえば、TryParse があるのにわざわざ Parse を try/catch する…みたいなのは、読みづらくなりがち💦 .NET の設計ガイドラインでも “通常の制御フローに例外を使うな” とされています📏 (Microsoft Learn)

✅ 例外が向く:DBやファイルI/O、外部API失敗、壊れた状態、前提違反 ❌ 例外が向かない:想定内の入力ミス、分岐の手段、探索の手段


ルールB:前提条件(引数チェック)は“入り口”で投げる🚪✨

「おかしい値を中に入れない」ために、メソッドの最初でガードします🛡️

public void RegisterUser(string email, int age)
{
ArgumentException.ThrowIfNullOrEmpty(email); // null/空文字なら即例外🧷
ArgumentOutOfRangeException.ThrowIfNegative(age); // 負なら即例外🔢

// ここから下は「前提が成立してる世界」🌈
}

ArgumentException.ThrowIfNullOrEmpty は .NET 10 でも公式APIとして載っています📚 (Microsoft Learn) ArgumentOutOfRangeException.ThrowIfNegative も .NET 10 のAPIにあります📚 (Microsoft Learn)


ルールC:例外型は“できるだけ具体的に”🎯

「何がダメだったか」を型で伝えると、呼び出し側が正しく扱えます😊

よく使う例外(超頻出)🌟

  • ArgumentNullException:nullダメ🙅‍♀️
  • ArgumentException:引数が不正😵
  • ArgumentOutOfRangeException:範囲外📉
  • InvalidOperationException:その状態ではできない😖
  • IOException:ファイル/ストレージ系の失敗📁

3) “握る(catch)”の基本ルール🤝🚧

ルールD:「回復できるときだけ catch」✅

Microsoft Learn の例外ベストプラクティスでも、回復できないなら catch しない が基本です📌 (Microsoft Learn)


catch していい理由は、だいたい3つ💡

① 回復できる(別の手段がある)🔁

例:ファイルが無いなら初期データを作る、など。

② 後片付けが必要(でも基本は using でOK)🧹

using が最優先✨(finally は「usingできない後片付け」に) これは公式の推奨でも using優先 です🧼 (Microsoft Learn)

③ 情報を足す / 例外を“翻訳”する🌐

例:IOException をそのままUIに見せず、ドメイン寄りの例外に変換する (この「翻訳」は第34章でさらに深掘りするけど、ここで基礎だけ触れるよ🫶)


4) “握りつぶし”が一番ダメ🙅‍♀️💣

次の2つは事故の香りがプンプンします👃💥

ダメ例①:空の catch(闇に消える…)🕳️

try
{
Save();
}
catch
{
// 何もしない ← バグが隠れる😱
}

ダメ例②:とりあえず成功扱い(静かに壊れる)🧨

try
{
return LoadConfig();
}
catch (Exception)
{
return new Config(); // しれっとデフォルト返す😇(地獄の始まり)
}

✅ 例外を握るなら、最低でも

  • 「回復した」ことが説明できる
  • もしくは「ログなどで追える」
  • もしくは「上位が扱いやすい形に変換する」 このどれかが必要だよ🛡️✨

5) 再スロー(throwし直し)の正しい作法🔥

ルールE:そのまま投げ直すなら throw;

throw;元のスタックトレースを保ったまま 上に投げ直します🗺️✨ Microsoft Learn でも「適切に再スローする」ことが重要とされています📌 (Microsoft Learn)

try
{
DoWork();
}
catch (IOException)
{
// ここで何か後処理(ログなど)をして…
throw; // 元の場所が分かるまま上に伝える✅
}

ルールF:情報を足したいなら “ラップ” + InnerException 🥟

try
{
repo.Save(items);
}
catch (IOException ex)
{
throw new TodoStorageException("保存に失敗しました。保存先にアクセスできません。", ex);
}

InnerException を持たせると、Exception Helper で原因を追いやすいです🔍✨ (Microsoft Learn)


6) finally と using:後片付けの王道👑🧼

基本は using が最強✨

IDisposable なもの(Stream, DB接続など)は using で必ず片付けよう🧹 例外が起きても片付けされるのが大事✅ (Microsoft Learn)

using var stream = File.OpenRead(path);
// ここで例外が起きても stream はDisposeされる🧼✨

finally は「usingできない後処理」に📌

StartSpinner();
try
{
DoWork();
}
finally
{
StopSpinner(); // 例外でも止める🌀➡️🛑
}

7) async とキャンセル:エラー扱いしない例外がある😴⏹️

非同期では、キャンセル時に OperationCanceledException 系が飛ぶことがあります🧵⚡ これは「想定された終了」のことも多いので、ログをエラーとして残すとノイズになりがち💦 Microsoft Learn でも キャンセルや非同期例外の扱い が項目としてあります📌 (Microsoft Learn)

よくある形(キャンセルは静かに抜ける)👇

try
{
await DoWorkAsync(ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
// キャンセルは正常系として扱うことも多い🕊️
}

when(例外フィルター)で条件付きにすると、余計なcatchを減らせてスッキリします✨


8) Visual Studioで例外を追う🔍🧑‍💻

Exception Helper(例外が起きた瞬間の情報)🧾✨

  • 例外の型、メッセージ、スタック、InnerException を確認できる
  • 「この例外で止まる/止まらない」を切り替えられる ✅ (Microsoft Learn)

Exception Settings(どの例外で止めるか)🧨📌

  • Debug → Windows → Exception Settings
  • 例外をチェックすると「ハンドルされてても投げられた瞬間に止まる」= first chance exception で止められるよ🛑 (Microsoft Learn)

9) ミニ演習📝✨(“握りつぶし”整理)

お題:雑なcatchを“意味のある例外処理”に直す💪🌸

**Before(ありがちな事故コード)**😇💣

public void SaveTodo(string path, string text)
{
try
{
File.WriteAllText(path, text);
}
catch (Exception)
{
// 何も言わない(闇)🕳️
}
}

**やること(3ステップ)**👇

  1. Exception をやめて、起こりうる例外に絞る🎯
  2. “回復できるか?”を決める(できないなら握らない)🤔
  3. どうしても握るなら、上位が扱える形にする📦

After(例:翻訳して上へ)

public void SaveTodo(string path, string text)
{
try
{
File.WriteAllText(path, text);
}
catch (IOException ex)
{
throw new TodoStorageException("Todoの保存に失敗しました(保存先にアクセスできません)。", ex);
}
}

ポイント🌟

  • “何が起きたか”が分かるメッセージになった📝
  • 原因(ex)は InnerException として保持🥟
  • UI側(呼び出し側)は TodoStorageException だけ見ればよくなる✨

10) ミニ演習📝✨(引数チェックを“入り口”へ)

Before(中で落ちる…)💥

public int Average(int total, int count)
{
return total / count; // count=0で爆発💣
}

After(入口で意味のある例外に)🛡️

public int Average(int total, int count)
{
if (count == 0)
throw new ArgumentOutOfRangeException(nameof(count), "count は 0 にできません。");

return total / count;
}

「どの引数が悪いか」が明確だと、直すのが速いよ🚀✨


11) AI活用🤖✨(安全に“例外の設計”を相談する)

AIは例外処理の提案が得意だけど、握りつぶし案も出しがちなので要注意⚠️😵‍💫 コツは「質問の仕方を固定」することだよ📌✨

そのまま使えるプロンプト例🪄

  • ✅ catchが必要か判定してもらう 「この try/catch は必要? “回復できる/できない”観点で理由つきで分類して」

  • ✅ 例外型の選定 「この条件ではどの例外型が適切? Argument系 / InvalidOperation など候補と理由を出して」

  • ✅ 握るなら最低限の形 「握りつぶし禁止で。上位が扱いやすい例外に翻訳するならどう設計する?」

  • ✅ 例外メッセージ改善 「利用者向け1行メッセージと、ログ向け詳細(idやpath)を分けて提案して」

AIの提案は 必ず差分レビュー + テスト実行 で確定ね✅🤖🛡️


12) 重要チェックリスト✅✨(これだけ覚えればOK)

throw(投げる)🧨

  • ✅ 通常フローに例外を使ってない? (Microsoft Learn)
  • ✅ 引数チェックは入口でやってる?🚪
  • ✅ 例外型は具体的?🎯
  • ✅ メッセージは「何が・なぜダメか」が短く書けてる?📝

catch(握る)🤝

  • ✅ 回復できるときだけ握ってる? (Microsoft Learn)
  • ✅ 空catchになってない?🕳️
  • ✅ 再スローは throw;(スタック維持)でできてる?🗺️ (Microsoft Learn)
  • ✅ デバッガ設定(Exception Settings)で原因を追える?🔍 (Microsoft Learn)

まとめ🌟

例外は「怖いもの」じゃなくて、失敗を正しく伝えて早く直すための道具です🧰✨ この章で身につけた 投げる場所 / 握る場所 / 握りつぶし禁止 / 正しい再スロー を守るだけで、バグ対応スピードが一気に上がるよ🚀💕

次の章からは、IDEのリファクタ機能でコード自体をどんどん整えていきます✂️🌸