この記事のポイント

  • ローカル変数・メンバー定義順序の整理原則を体系化
  • レビューで初期化順・責務境界をどう確認するか解説
  • 実務コードの改善例・レビュー観点を提示

定義順序のレビューが重要になる背景

① C++は初期化順序がソース上の宣言順に依存する

  • メンバー初期化は宣言順固定で処理される
  • 初期化リストの並び順は無関係

② ローカル変数はスコープ順と依存関係順が混乱しやすい

  • 依存変数より後に宣言される危険が生じる

③ 保守時の読解コストが急上昇

  • 定義順不整合が読解負荷の温床になる

④ バグ耐性が低下する

  • 初期化順序依存バグは発見困難

クラスメンバー定義順序の設計原則

原則 説明
① 依存関係順 依存されるメンバーを先に配置
② 参照型・const優先配置 初期化責任明示化
③ 論理ブロック順序化 機能単位でまとめる
④ API公開順と一致可能性配慮 保守者視点で整理

レビュー着眼点

「この定義順は保守者に責務依存を伝えられているか?」


良い実装例(クラスメンバー編)

class Session {
public:
    Session(Database& db, const std::string& token)
        : db_(db), token_(token), id_(generateId()) {}

private:
    Database& db_;            // 依存注入
    const std::string token_; // 固定属性
    int id_;                  // 動的生成
};

良い設計理由

  • 依存関係順
  • const/参照が先行
  • 動的生成は最後

ローカル変数定義順序の設計原則

原則 説明
① 使う直前に定義 スコープ最小化
② 依存変数先行定義 依存構造明示
③ 無駄な初期値代入回避 事前値・仮置き定義抑止
④ 変数用途ブロック化 責務単位整理

レビュー補助質問

「この変数はいつ必要になるのか?」


良い実装例(ローカル変数編)

void process() {
    const int count = getCount();

    std::vector<int> data(count);
    for (int i = 0; i < count; ++i) {
        data[i] = calculate(i);
    }

    int sum = 0;
    for (const auto& v : data) {
        sum += v;
    }

    log(sum);
}

良い設計理由

  • 使う直前定義
  • 初期値不要箇所なし
  • 論理ブロック化明確

良くない実装例: ケース1

class Session {
public:
    Session(Database& db, const std::string& token)
        : id_(generateId()), token_(token), db_(db) {}

private:
    int id_;
    const std::string token_;
    Database& db_;
};
@Reviewer
メンバー定義順と初期化順序が一致していません。依存関係順に並び替えてください。

改善例

Database& db_;
const std::string token_;
int id_;

ケース2: ローカル変数のスコープ広げすぎ

void compute() {
    int sum;
    sum = 0;

    int i;
    for (i = 0; i < 100; ++i) {
        sum += i;
    }

    log(sum);
}
@Reviewer
変数は使用直前に定義してください。スコープ最小化で読みやすくなります。

改善例

int sum = 0;
for (int i = 0; i < 100; ++i) {
    sum += i;
}

ケース3: 不要な仮置き初期化

void process() {
    int value = 0;
    if (condition()) {
        value = calculate();
    }
    use(value);
}
@Reviewer
不要な初期値代入は避けてスコープを絞り込みましょう。

改善例

if (condition()) {
    int value = calculate();
    use(value);
}

ケース4: 論理ブロック散在

void process() {
    int count = getCount();
    int sum = 0;
    std::vector<int> data(count);

    for (int i = 0; i < count; ++i) {
        data[i] = calculate(i);
    }

    for (const auto& v : data) {
        sum += v;
    }

    log(sum);
}
@Reviewer
初期化・処理・集計はブロックごとに整理してください。

改善例

const int count = getCount();
std::vector<int> data(count);

for (int i = 0; i < count; ++i) {
    data[i] = calculate(i);
}

int sum = 0;
for (const auto& v : data) {
    sum += v;
}

log(sum);

ケース5: グローバル定義順序の設計意図崩壊

class Engine {
private:
    Logger logger_;
    Config config_;
    Database db_;
};
@Reviewer
依存順でconfig→db→loggerに並び替えを検討してください。責務依存が逆転しています。

改善例

Config config_;
Database db_;
Logger logger_;

定義順序レビューの実務意義

防止崩壊 設計効果
順序依存バグ 初期化安全性確保
保守混乱 可読性統一
仮置き設計 スコープ適正化
読解疲労 ロジック流れ一貫性

観点チェックリスト


まとめ

レビューアーの思考は常にこう整理する:

「この変数の定義順は責務と依存関係を正しく表現しているか?」

  • 定義順序は設計責務の自己文書化
  • 初期化順序事故は初期化責務分散の兆候

「責務可視化順序レビュー」こそC++レビュー成熟度の基本技法です。