この記事のポイント

  • 例外透過設計(Exception Transparency)の設計意図をレビューアーが読み取る技術を整理
  • 伝播責務とハンドリング責務の分離判断をレビューで具体指摘可能にする
  • 透過設計を採るべき場面・採らないべき場面の線引きをレビュー観点で学習する

そもそも例外透過設計とは

例外透過(exception transparency) とは:

  • 自層では例外を握り潰さず、上位層に伝播させる設計方針
  • 例外を契約の一部として公開する
  • 層単位の責務整理を容易化する代わりに、ハンドリング責務は上位へ委譲
[発生層] → [中間層(透過)] → [ハンドリング層(収束)]

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

  • 握り潰し設計を抑止する役割
  • 層越境した副作用干渉を排除できるか確認
  • ハンドリング責務と通知責務を混同していないか確認
  • 全体契約として伝播経路を明示的に管理するため

透過設計は責務分界のレビューで極めて重要な論点になります。

レビューアー視点

  • この層でハンドリングすべき責務を持つのか?
  • 復旧可能性が本当に存在しないのか?
  • 透過設計方針がプロジェクトで統一されているか?
  • API契約として例外伝播を正しく文書化しているか?
  • ログや監視責務は別途持たれているか?

開発者視点

  • 原則「復旧不能なら透過する」
  • 業務層が復旧責務を持つ範囲を限定する
  • 透過契約に依存しすぎず必要な文脈付加は実施する
  • API契約上の明示責務整理を意識する

良い実装例

完全透過設計

完全透過型の中間層
#include <stdexcept>

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

class Service {
public:
    void process() {
        repository_.save();  // 透過的に上位に伝播
    }
private:
    Repository repository_;
};
  • Service層ではハンドリング責務を持たず透過
  • Application層が最終捕捉

文脈付加透過設計

文脈情報を付加して透過
class Service {
public:
    void process() {
        try {
            repository_.save();
        } catch (const std::exception& e) {
            throw std::runtime_error(std::string("Service::process failed: ") + e.what());
        }
    }
};
  • 原因情報保持+責務層情報付加

レビュー観点

  • 業務責務層がハンドリングを持たず透過設計になっているか
  • 握り潰し(catch …)が中間層に混在していないか
  • 文脈情報の付加は実施されているか
  • API契約ドキュメントに透過例外が明示されているか
  • 上位層catch戦略が整理されているか

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

中間層握り潰し型
class Service {
public:
    void process() {
        try {
            repository_.save();
        } catch (...) {
@Reviewer
中間層でのcatch(...)は障害調査困難化を招きます。透過設計方針に統一し、上位に伝播させてください。
} } };

改善例

改善例(透過設計統一)
class Service {
public:
    void process() {
        repository_.save();
    }
};

良くない実装例: ケース2(上位層伝播整理不足)

上位層catch設計放棄
void runApplication() {
    service.process();  // 透過されてきた例外を無視
@Reviewer
透過設計を採るなら、上位層でのcatch網羅設計が必要です。復旧・通知・監視を統合してください。
}

改善例

改善例(収束catch統合)
void runApplication() {
    try {
        service.process();
    } catch (const std::exception& e) {
        logger.error("Application failure: {}", e.what());
        notifyMonitoring(e);
    }
}

良くない実装例: ケース3(透過契約破綻)

API契約矛盾例
class Repository {
public:
    void save();  // 仕様上「例外は投げません」とAPI契約に明記
};

%% @Reviewer 例外透過設計を採用しているならAPI契約上もthrows可能性を正確に文書化してください。契約整合性が失われています。

改善例

改善例(透過契約明示)
class Repository {
public:
    void save();  // throws std::runtime_error
};

PlantUML:透過設計責務フロー

UML Diagram

ロギング戦略統合例

透過+収束設計
try {
    service.process();
} catch (const LogicException& e) {
    logger.warn("Validation failure: {}", e.what());
} catch (const RuntimeException& e) {
    logger.error("Runtime failure: {}", e.what());
    notifyOps(e);
} catch (const std::exception& e) {
    logger.fatal("Unhandled failure: {}", e.what());
}
  • 透過は収束先のcatch網羅設計とセットで成立

観点チェックリスト

まとめ

レビューアーが透過設計レビューで問うべきは
「この層が例外を止める責任を本当に持つべきか?」
です。

  • 中間層透過 → 責務境界整理
  • 収束層捕捉 → ログ・通知統合

透過設計レビューは「握り潰し文化を作らせない防御壁」です。
レビュー現場では「この層のcatchは不要です、透過統一しましょう」
責務分界を明示指摘するレビュー文化が品質を高めます。