この記事のポイント

  • 例外変換(ラッピング・再スロー)設計のコスト意識をレビューアーが読み取る技術を整理
  • 診断性・パフォーマンス・保守性の観点から変換設計をレビューで具体的に指摘可能にする
  • 変換コストを「設計責務コスト」として体系化して判断できる力を身につける

そもそも「例外変換コスト意識」とは

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:例外変換責務フロー

UML Diagram

変換設計指針

変換目的 許容可否 設計ポイント
層責務越境 許容 名詞型統一
階層内整形 非推奨 透過優先
診断情報付加 許容 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契約 → 外部遮断責務

レビュー現場では
「変換は最小責務移譲のみに限定しましょう」
と設計論点を整理提案できるレビュー文化が現場品質を支えます。