C++17 try-catchスコープ設計レビュー|例外ハンドリング範囲と責務分界をレビューアーがどう指摘すべきか
この記事のポイント
- 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 (...) {
@Reviewercatch(...)の握り潰しは障害診断不能になります。例外型を限定し、ログ出力も統合してください。 }
}改善例
改善例(明示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;
}
@Reviewerrepository層の例外は上位に伝播させ、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スコープ責務図
ロギング戦略統合例
スコープ統合型の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スコープの範囲を整理してください」と言語化できるレビュー技術が重要です。
