この記事のポイント

  • エラーコードと例外混在設計のレビュー観点を体系整理
  • 二重エラーハンドリング崩壊をレビューアーが整理・統一提案する技術を習得
  • 設計責務を統一軸で収束し設計文化をレビュー支援できる

そもそもエラーコードと例外混在とは何か

C++開発では「エラーは例外で処理するべきか?コードで返すべきか?」が常に議論される。
混在設計とは両方を無自覚に併用してしまう危険領域を指す。

SaveResult save();  // エラーコード方式
void save();        // 例外方式

混在が進行すると、以下の問題をレビュー段階で助長する。

問題分類 発生現象
責務崩壊 どちらが契約責務か判別困難
再現困難 同一障害でも片方だけ拾われる
保守困難 API設計統一不在
運用困難 監視通知が経路依存化

レビューアーは混在を許容するか統一するかを設計議論で収束させる技術支援を担う。

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

  • 曖昧設計のまま運用突入が頻発
  • レビュー初期で「どちらに統一するのか?」を問わないと保守爆発
  • 現場文化に流されると「歴史的混在設計」が再生産される
  • レビューアーが統一軸を提示し設計文化を育成する必要がある

レビューアー視点

  • このプロジェクトの統一方針はどちらか?
  • 統一適用範囲が層毎に明文化されているか?
  • 例外禁止領域が宣言されているならコード統一されているか?
  • API公開契約が統一粒度で整理されているか?
  • 運用通知経路が統一されているか?

開発者視点

  • エラーコード方式なら全層統一方針徹底
  • 例外方式なら契約違反と障害通知責務を整理分離
  • 公開API契約型のみが利用者視点で露出
  • 混在経路は封じ込め層で吸収整理

良い設計例

例1:例外統一パターン

例外方式統一
void saveUser(int id) {
    if (id <= 0) {
        throw std::invalid_argument("Invalid id");
    }
    if (!db.save(id)) {
        throw DatabaseException("DB failure");
    }
}
  • 契約違反 → invalid_argument
  • 障害通知 → custom exception

例2:エラーコード統一パターン

エラーコード方式統一
enum class SaveResult {
    Success,
    InvalidId,
    DBFailure
};

SaveResult saveUser(int id) {
    if (id <= 0) return SaveResult::InvalidId;
    if (!db.save(id)) return SaveResult::DBFailure;
    return SaveResult::Success;
}
  • 契約条件も含め全てコード収束

レビュー観点

  • 全API契約粒度で統一軸が固定されているか
  • 契約違反vs障害通知の分離が整理されているか
  • 混在経路が封じ込め層で吸収されているか
  • 運用監視通知が統一ルートで整理されているか
  • 移植性・利用性・保守性を含めて統一軸が合理説明可能か

良くない実装例: ケース1(層毎に混在)

API層混在型
class Repository {
public:
    SaveResult save(const User& u); // エラーコード
};

class Service {
public:
    void process(const User& u); // 例外方式
@Reviewer
層毎に例外方式が分断しています。全層統一責務で設計整理してください。

改善例

改善例(例外統一)
class Repository {
public:
    void save(const User& u);
};

良くない実装例: ケース2(内部混在)

関数内部で混在
SaveResult saveUser(const User& u) {
    if (!db.connect()) throw DBConnectException();
    if (!db.save(u)) return SaveResult::DBFailure;
@Reviewer
同一責務関数内で混在しています。統一方式に整理してください。
}

改善例

改善例(エラーコード統一)
if (!db.connect()) return SaveResult::DBConnectFailure;

良くない実装例: ケース3(監視通知分断)

通知経路分断
void process() {
    try {
        service.save();
    } catch (const std::exception& e) {
        monitoring.notify("EXCEPTION_FAILURE");
    }

    SaveResult result = dao.save();
    if (result != SaveResult::Success) {
        // 通知未統合
@Reviewer
通知経路が例外系/コード系で分断しています。統一監視通知経路に整理してください。
} }

改善例

改善例(通知統合)
notifyFailure(result); // 共通通知層整理

PlantUML:混在統一責務整理図

UML Diagram

適用戦略整理表

統一方針 適用ケース 技術背景
例外統一 業務系API C++設計文化順当
コード統一 組込・RTOS系 ヒープ禁止文化順当
層内透過統一 パフォーマンス層 高速層に適合
封じ込め吸収 外部ライブラリ接続層 透過吸収責務整理

レビューアーは層別適用文化構築支援が役割になる。

API契約統一例

公開API例
// 統一型宣言
class SaveResult {
public:
    static SaveResult Success();
    static SaveResult ValidationError();
    static SaveResult DBFailure();

    bool isSuccess() const;
    std::string message() const;
};
  • 例外封じ込め後に契約型へ収束

ロギング統合例

統一障害通知例
SaveResult result = service.saveUser(u);
if (!result.isSuccess()) {
    logger.error("Save failed: {}", result.message());
    monitoring.notify("SAVE_FAILURE", result.message());
}
  • 通知経路が例外方式/コード方式に依存しない

混在崩壊を引き起こす代表的理由

崩壊原因 説明 レビュー防止策
開発者間文化差異 経験依存化 初期設計宣言義務化
ライブラリ仕様流入 Boost系透過 Adapter層封じ込め義務
移行履歴残存 歴史的コード混在 大改修時設計統一
監視系設計不在 通知系分断 共通通知層先行整備

レビューアーは設計崩壊要因の早期可視化役割を担う。

観点チェックリスト

まとめ

レビューアーがエラーコードと例外混在設計レビューで常に問うべきは
「この責務は誰が最終的に統一して保証管理しているか?」
です。

  • 統一契約文化 → 入口段階で宣言義務
  • 封じ込め吸収文化 → 混在防止層設計
  • 監視統合文化 → 通知系先行整理
  • 保守性文化 → 将来増殖予防

レビュー現場では
「統一設計方針をレビュー文化で初期固定する」
ことが設計品質を大きく決定づけます。