この記事のポイント

  • try-catchのスコープ設計をレビューアーが読むべき観点を整理
  • 過剰広域catch・過小局所catchの危険性を実務指摘例で理解
  • 責務単位と復旧可能性からスコープ設計をレビューできる

そもそもtry-catchスコープ設計とは何か

例外ハンドリングで重要なのは
「どの範囲まで処理し、どこから先は上位に任せるか」
という責務分界の設計です。

スコープ設計 概要 典型責務
狭域catch 個別操作単位でキャッチ リトライ・補正・局所復旧
広域catch 処理単位全体を包含 ログ・障害通知・伝播停止

レビューアーは「今ここで止める理由は妥当か?」を設計から読み取ります。

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

  • try-catch位置は障害制御責任を決める設計責務
  • スコープ曖昧 → 責務逆流、握り潰し、多重catch肥大
  • 広域catch濫用 → 原因箇所特定不能
  • 狭域catch濫用 → コード過剰細分化・分岐肥大

レビューアー視点

  • 復旧責務が自層で成立しているか?
  • 副作用がcommit済みかrollback可能か?
  • ログ・監視責務がここで持たれているか?
  • 握り潰しになっていないか?
  • 業務境界単位に責務粒度が一致しているか?

開発者視点

  • 原則「復旧可能範囲のみcatchする」
  • 上位層伝播設計を積極活用する
  • 副作用順序を意識した分岐を構成する
  • 監視設計・ロギング方針とセットで考える

良いスコープ設計例

局所復旧可能catch

局所復旧の例
#include <stdexcept>
#include <iostream>

class FileProcessor {
public:
    void writeFile(const std::string& path) {
        try {
            saveFile(path);
        } catch (const std::ios_base::failure& e) {
            std::cerr << "Retry after failure: " << e.what() << std::endl;
            saveFile(path); // 再試行
        }
    }

private:
    void saveFile(const std::string& path) {
        // I/O失敗可能性
    }
};
  • I/Oは一時的失敗が多く、局所retryが成立する

上位責務統合catch

アプリ層の収束catch
void runApplication() {
    try {
        service.execute();
    } catch (const ApplicationException& e) {
        logger.error("Application fatal error: {}", e.what());
        notifyMonitoring(e);
    }
}
  • 業務責務全体を統合的に集約処理

レビュー観点

  • catchスコープが副作用単位と整合しているか
  • 自層復旧可能なものだけ局所catchしているか
  • 上位伝播が正しく活用されているか
  • ロギング・監視責務がcatch内に設計されているか
  • 握り潰し設計が含まれていないか

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

握り潰しで障害特定不能
void saveUser(const User& user) {
    try {
        repository.save(user);
    } catch (...) {
@Reviewer
catch(...)の握り潰しは障害診断不能になります。例外型を限定し、ログ出力も統合してください。
} }

改善例

改善例(明示catch+ログ統合)
void saveUser(const User& user) {
    try {
        repository.save(user);
    } catch (const std::exception& e) {
        logger.error("Failed to save user: {}", e.what());
        throw;
    }
}

良くない実装例: ケース2(過剰狭域catch)

過剰な細分化で保守性低下
void saveAndLog(const User& user) {
    try {
        repository.save(user);
    } catch (const std::exception& e) {
        logger.error("Repository error: {}", e.what());
    }

    try {
        auditLogger.write(user);
    } catch (const std::exception& e) {
        logger.error("Audit log error: {}", e.what());
    }
@Reviewer
各操作単位でのcatch分散は保守性低下要因です。全体のatomicity保証範囲でスコープ統合してください。
}

改善例

改善例(業務単位でcatch統合)
void saveAndLog(const User& user) {
    try {
        repository.save(user);
        auditLogger.write(user);
    } catch (const std::exception& e) {
        logger.error("Failed to process user save sequence: {}", e.what());
        throw;
    }
}

良くない実装例: ケース3(上位伝播設計放棄)

層責務混在
void service() {
    try {
        repository.save();
    } catch (const std::exception& e) {
        std::cerr << "Repository error: " << e.what() << std::endl;
    }
@Reviewer
repository層の例外は上位に伝播させ、service層が責務として統合捕捉してください。層間責務分界が崩れています。
}

改善例

改善例(責務分界統合)
void repository() {
    throw std::runtime_error("DB failure");
}

void service() {
    repository();
}

void application() {
    try {
        service();
    } catch (const std::exception& e) {
        logger.error("Service failure: {}", e.what());
    }
}

PlantUML:try-catchスコープ責務図

UML Diagram

ロギング戦略統合例

スコープ統合型のcatch
void run() {
    try {
        userService.register(user);
    } 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("Unexpected failure: {}", e.what());
    }
}
  • 層分類×スコープ統合 → 可読性向上

観点チェックリスト

まとめ

レビューアーが常に問うべきは
「ここで例外を止める責任は誰が持つべきか?」
という責務境界の可視化です。

  • 狭すぎるcatch → 分岐肥大
  • 広すぎるcatch → 原因消失
  • 握り潰しcatch → 最悪設計

設計者の責務意識と復旧可能性の整理状況をレビューアーが読み解き、
具体的な指摘として「catchスコープの範囲を整理してください」と言語化できるレビュー技術が重要です。