C++17例外再スロー時の情報補足レビュー|rethrow設計と文脈付加の責務をレビューアーがどう指摘すべきか
この記事のポイント
- 例外再スロー(rethrow)設計での情報補足責務をレビューアーが読み取る視点を整理
- 上流伝播時の文脈情報付加戦略をレビューで具体的に指摘できるようになる
- 障害調査容易性を高めるrethrow設計パターンを実務観点で整理
そもそも例外再スロー時の情報補足とは
C++で例外が発生した場合、そのまま上位に伝播させるだけでは
「どの経路で失敗に至ったか」
が見えなくなるケースが多い。
- 直接throw: 単層原因はわかる
- 多層rethrow: 経路情報が消失しやすい
再スロー時に文脈情報を上乗せすることで、
障害診断やログ上の原因特定速度が大きく改善される。
なぜこれをレビューするのか
- 生のままrethrow → 調査現場で経路推測困難
- catch → logだけ → throw; → 情報断絶
- 障害現場で「誰がどの処理で再throwしたのか」を残す必要がある
レビューアーは「このrethrowは文脈付加されているか?」を確認すべき。
レビューアー視点
- どの層の責務で情報を付加すべきか
- 追加情報の粒度が診断支援になっているか
- スタックトレース非依存でも経路が辿れるか
- 再スロー時の新旧例外情報整理が適切か
- 副作用順序に矛盾しないか
開発者視点
- catch → log → throw; は最低限
- 可能なら例外型を差し替え+元例外埋込する
- ドメイン固有分類名詞化の補助を行う
- 複数層で「階層的ラップ」を設計する
良い実装例
基本パターン:文脈補足再スロー
文脈情報を上乗せ
#include <stdexcept>
#include <string>
class Repository {
public:
void save() {
throw std::runtime_error("DB failure");
}
};
class Service {
public:
void process() {
try {
repository_.save();
} catch (const std::exception& e) {
throw std::runtime_error(std::string("Service::process failed - ") + e.what());
}
}
private:
Repository repository_;
};- 上位層名を追加
- 再throw後も経路が判読可能
発展パターン:独自例外型に文脈再整理
独自型で責務移譲
class ServiceException : public std::runtime_error {
public:
explicit ServiceException(const std::string& msg)
: std::runtime_error(msg) {}
};
class Service {
public:
void process() {
try {
repository_.save();
} catch (const std::exception& e) {
throw ServiceException(std::string("Process failed: ") + e.what());
}
}
private:
Repository repository_;
};- catch側はServiceExceptionのみ捕まえれば良くなる
- 責務階層で分類整理が進む
レビュー観点
- 例外型は責務名詞化されているか
- 元例外のwhat()情報が保持されているか
- 層名・関数名・引数など経路情報が埋め込まれているか
- 単なるlog+throw;で満足していないか
- 障害現場で調査可能な文脈情報量になっているか
良くない実装例: ケース1(再スロー情報補足無し)
何も補足しない再スロー
void service() {
try {
repository.save();
} catch (const std::exception& e) {
throw;
@Reviewer再スローするならService層での文脈情報を補足してください。責務境界が読めなくなります。 }
}改善例
改善例(文脈付加)
void service() {
try {
repository.save();
} catch (const std::exception& e) {
throw ServiceException(std::string("service() failure: ") + e.what());
}
}良くない実装例: ケース2(logだけして放流)
ログ出力のみで放流する例
void service() {
try {
repository.save();
} catch (const std::exception& e) {
logger.error("Repository failed: {}", e.what());
throw;
@Reviewerログだけではなく、再スロー側にも文脈付加してください。調査時に検索可能性が上がります。 }
}改善例
改善例(ログ+文脈付加)
void service() {
try {
repository.save();
} catch (const std::exception& e) {
logger.error("Repository failed: {}", e.what());
throw ServiceException(std::string("service() failure after repository: ") + e.what());
}
}良くない実装例: ケース3(型継承放棄)
元例外情報断絶型
class FatalError {
public:
FatalError(const std::string& msg): msg_(msg) {}
std::string what() const { return msg_; }
private:
std::string msg_;
};%% @Reviewer std::exception系継承を行ってください。汎用catch不能になります。また再スロー時には旧例外の情報も保持すべきです。
改善例
改善例(標準継承接続+文脈付加)
class FatalApplicationError : public std::runtime_error {
public:
FatalApplicationError(const std::string& ctx, const std::exception& cause)
: std::runtime_error(ctx + ": " + cause.what()) {}
};PlantUML:rethrow情報補足設計フロー
ロギング統合例
再スロー文脈を統合出力
try {
service.process();
} catch (const ServiceException& e) {
logger.error("Service layer failed: {}", e.what());
} catch (const std::exception& e) {
logger.fatal("Unhandled exception: {}", e.what());
}- 再throw情報はwhat()統合管理で出力
観点チェックリスト
まとめ
レビューアーはrethrow設計時に常に問うべきは
「上流に伝える情報は充分か?」
です。
- 再現条件:入力情報を埋め込む
- 経路責務:層名・関数名を埋め込む
- 原因情報:旧例外のwhat()保持
文脈付加設計は"現場支援型レビュー"の最大テーマです。
レビュー現場では「再スロー時の情報補足設計を整理しましょう」と具体指摘できる技術力が求められます。
