C++17 例外変換時のコスト意識レビュー|ラップ・変換設計が生む性能・保守・診断性コストをレビューアーが読み解く
この記事のポイント
- 例外変換(ラッピング・再スロー)設計のコスト意識をレビューアーが読み取る技術を整理
- 診断性・パフォーマンス・保守性の観点から変換設計をレビューで具体的に指摘可能にする
- 変換コストを「設計責務コスト」として体系化して判断できる力を身につける
そもそも「例外変換コスト意識」とは
C++の例外は自由に型を入れ替えてラッピング・再スロー可能であるが、
安易に変換を重ねると以下の問題が発生する。
| 発生コスト | 影響内容 |
|---|---|
| 診断コスト | 原因例外情報が階層内で消失 |
| パフォーマンスコスト | 例外発生時の文字列構築・メモリコピー肥大 |
| 保守コスト | 例外型増殖でレビュー不能化 |
| API契約コスト | 透過すべき型と変換すべき型の線引き消失 |
レビューアーは「この変換は誰のために設計されているのか?」を読み解く必要がある。
なぜこれをレビューするのか
- 診断可能性がログ側から読めなくなるケースが発生
- 型設計の責務整理が崩れると層責務読解が不能化
- 例外発生経路コストが無駄に重くなる設計が量産
- 保守フェーズで誰も手を付けられなくなる危険ゾーンに直結
レビュー段階で「変換は必要最小限か?」を設計観点で読み解くことが重要。
レビューアー視点
- 誰がこの型に変換した責務を持つのか?
- 診断性を上げる目的に適合しているか?
- 上流例外情報が消失していないか?
- API契約責務に即して型変換しているか?
- 不要な文字列構築コストを累積していないか?
開発者視点
- 変換は原則「責務層変化時」のみ許容
- 階層越えしない内部層では透過統一が原則
- 診断情報維持は最低限の責任
- 契約型→契約型の境界整理でコスト抑止
良い実装例
例1:層責務移行時のみ型変換
責務境界移行時変換
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 ServiceException(std::string("Service processing failed: ") + e.what());
}
}
private:
Repository repository_;
};- 層境界越え時のみ型変換
- what()情報は全保持
例2:診断情報付加+カスタム型変換
文脈情報追加
class FileReadException : public std::runtime_error {
public:
FileReadException(const std::string& file, const std::exception& cause)
: std::runtime_error("File read failed (" + file + "): " + cause.what()) {}
};- 責務情報付加 → 調査性向上
レビュー観点
- 層越境時の責務名詞化変換になっているか
- 元例外情報(what())が完全維持されているか
- 文字列構築の過剰多重化が抑制されているか
- API契約責務境界に即した型設計になっているか
- 不要層内多段変換が起きていないか
良くない実装例: ケース1(層内多段変換)
層内部で変換濫用
void repository() {
throw std::runtime_error("DB failure");
}
void repositoryWrapper() {
try {
repository();
} catch (const std::exception& e) {
throw RepositoryLayerException(e.what());
@Reviewer同一責務層内の冗長変換は抑制してください。層内は透過統一が原則です。 }
}改善例
改善例(透過統一)
void repositoryWrapper() {
repository();
}良くない実装例: ケース2(元例外情報消失)
情報断絶型
void service() {
try {
repository();
} catch (...) {
throw ServiceException("Service failure");
@Reviewer元例外情報が消失しています。what()埋め込みで診断可能性を維持してください。 }
}改善例
改善例(情報保持)
throw ServiceException("Service failure: " + std::string(e.what()));良くない実装例: ケース3(過剰ネスト構築)
文字列構築過剰
void convert() {
try {
process();
} catch (const std::exception& e1) {
try {
recover();
} catch (const std::exception& e2) {
throw std::runtime_error("Recovery failed: " + std::string(e2.what()) + " after " + e1.what());
@Reviewer文字列構築の過剰ネストは読解困難化します。層責務単位で整理し段階出力に整理してください。 }
}
}改善例
改善例(階層整理)
throw RecoveryFailure(e2.what(), e1.what());PlantUML:例外変換責務フロー
変換設計指針
| 変換目的 | 許容可否 | 設計ポイント |
|---|---|---|
| 層責務越境 | 許容 | 名詞型統一 |
| 階層内整形 | 非推奨 | 透過優先 |
| 診断情報付加 | 許容 | what()保持必須 |
| 外部API透過 | 禁止 | 封じ込め必須 |
| 再発行文字列増殖 | 非推奨 | スタック風追跡整理 |
レビューアーはこれら表で即時分類できる訓練を積む。
API契約設計整理
API透過型崩壊
void apiCall() throws ThirdPartyException;
@Reviewer外部例外透過は禁止です。自プロダクト契約型へ変換してください。
改善例
改善例(API契約統一)
void apiCall() throws InternalGatewayException;ロギング統合例
診断性重視統合
try {
service.process();
} catch (const ServiceException& e) {
logger.error("Service failure: {}", e.what());
}- 統合what()のみ設計維持
コスト意識のレビュー視点整理
| コスト分類 | 具体例 | 指摘観点 |
|---|---|---|
| 診断性コスト | 元例外消失 | what()保持確認 |
| パフォーマンスコスト | 文字列構築重複 | 結合処理最小化 |
| 保守コスト | 型種類爆発 | 層名詞設計確認 |
| API契約コスト | 外部型透過 | 契約型統一要求 |
観点チェックリスト
まとめ
レビューアーが例外変換コスト設計で常に問うべきは
「この変換は誰にとってどんな責務を果たすものか?」
です。
- 層移動 → 型変換責務
- 層内 → 透過統一責務
- 診断性 → 情報保持責務
- API契約 → 外部遮断責務
レビュー現場では
「変換は最小責務移譲のみに限定しましょう」
と設計論点を整理提案できるレビュー文化が現場品質を支えます。
