この記事のポイント

  • 例外漏洩(未処理例外)のレビュー観点を整理できる
  • catch網羅性と責務分界線を設計レビューで読み取る技術を学べる
  • 開発者が迷わぬレビュー指摘例を実践的に理解できる

そもそも例外漏洩とは

C++における例外漏洩(Exception Leakage)とは、想定される例外が呼び出し元に伝播し続け、処理不能状態まで到達してしまう設計上の欠陥を指します。

  • ハンドリングすべき責務層でキャッチされずに伝播
  • システムの外側まで例外が漏れ、std::terminateが発動
  • ログ出力・通知・復旧処理などが行われない

レビューアーは例外発生点だけでなく、例外がどの層で止められる設計なのかを読み取る責務を負います。

なぜこれをレビューするのか

  • 例外伝播設計は責務境界と密接に結びつく
  • 層ごとのハンドリング契約が未整備だと障害が表面化する
  • catch網羅性が甘いと復旧不能障害や障害分析不能状態になる
  • ログ・監視・通知責務の所在が不明確になる

レビューアー視点

  • 例外はどの層で収束させるのかを設計として確認
  • 例外階層型の整理が行われているか
  • catch節で何を握り潰し何を伝播させるのか明確化されているか
  • 必要に応じて上位層に情報付加(ラップ)されているか

開発者視点

  • 自層の責務範囲で処理可能かを見極める
  • 処理不能なら上位に文脈付加して伝播する
  • 決して例外を握り潰さない
  • ログ・通知・監視の発火点はcatch設計とセットで整備

良い実装例

責務分界が整理された例外伝播
#include <stdexcept>
#include <string>
#include <iostream>

class Repository {
public:
    void saveData() {
        throw std::runtime_error("DB connection failed");
    }
};

class Service {
public:
    void process() {
        try {
            repository_.saveData();
        } catch (const std::exception& e) {
            throw std::runtime_error(std::string("Service processing failed: ") + e.what());
        }
    }
private:
    Repository repository_;
};

class Application {
public:
    void run() {
        try {
            service_.process();
        } catch (const std::exception& e) {
            logError(e);
        }
    }

    void logError(const std::exception& e) {
        std::cerr << "Fatal Error: " << e.what() << std::endl;
    }
private:
    Service service_;
};
  • 層ごとに責務分界されたcatch設計
  • 上位層はログ通知・監視責務を担当
  • 途中で握り潰さず文脈付加で伝播

レビュー観点

  • 業務責務の層単位で例外収束設計が取られているか
  • 握り潰し(空catch)が存在していないか
  • catch網羅性が十分か(論理網羅・型網羅)
  • ログ・通知がcatch設計に統合されているか
  • 例外情報が失われずラップされているか

良くない実装例: ケース1(握り潰し)

握り潰しによる障害隠蔽
#include <stdexcept>
#include <iostream>

class Repository {
public:
    void saveData() {
        throw std::runtime_error("DB failure");
    }
};

class Service {
public:
    void process() {
        try {
            repository_.saveData();
        } catch (...) {
            // do nothing
@Reviewer
例外を握り潰しています。catch(...)は例外情報を破棄し、障害原因の特定不能に繋がります。最低限ログ出力または上位伝播させてください。
} } private: Repository repository_; };

問題点

  • catch節で障害情報が完全消失
  • 監視・通知不能な設計欠陥

改善例

改善例(ログ通知統合)
#include <stdexcept>
#include <iostream>

class Service {
public:
    void process() {
        try {
            repository_.saveData();
        } catch (const std::exception& e) {
            std::cerr << "Service error: " << e.what() << std::endl;
            throw; // 上位へ伝播維持
        }
    }
private:
    Repository repository_;
};

良くない実装例: ケース2(catch網羅性不足)

catch範囲の狭さで漏洩
#include <stdexcept>
#include <iostream>

class Service {
public:
    void process() {
        try {
            repository_.saveData();
        } catch (const std::invalid_argument& e) {
            std::cerr << "Invalid arg: " << e.what() << std::endl;
        }
@Reviewer
catch対象が限定されすぎています。他のstd::exception系や実行時障害も適切に網羅してください。
} private: Repository repository_; };

問題点

  • 論理的に発生可能な例外型を網羅していない
  • キャッチ漏れで上位伝播が不意に発生する

改善例

改善例(型網羅性強化)
#include <stdexcept>
#include <iostream>

class Service {
public:
    void process() {
        try {
            repository_.saveData();
        } catch (const std::invalid_argument& e) {
            std::cerr << "Invalid arg: " << e.what() << std::endl;
        } catch (const std::exception& e) {
            std::cerr << "Other exception: " << e.what() << std::endl;
        }
    }
private:
    Repository repository_;
};

PlantUML:例外伝播とcatch網羅性フロー

UML Diagram

観点チェックリスト

まとめ

レビューアーの最大の役割は
「ここで例外が止まるのか?それとも流れるのか?」
という例外経路設計の意図を読み取ることにあります。

例外漏洩を放置すれば、

  • 障害発生時の原因特定不能
  • システム異常終了
  • ログ空白

という致命的障害に直結します。

レビュー現場ではcatch設計を流れ全体で可視化し、責務層を越境させない設計指摘を習慣にしていくことが重要です。