この記事のポイント

  • static・グローバル変数使用が設計崩壊に繋がる理由を理解する
  • レビューで責務境界の歪みを早期に検出する力を養う
  • 静的初期化順序や並列競合など実務上の危険信号を整理する

グローバル変数・static変数は
「便利さの裏にある設計負債の温床」 です。
レビューアーは「使用しているか否か」ではなく
スコープ設計と責務の整理具合 を読み取る必要があります。


1. そもそもstatic変数・グローバル変数とは?

1-1. グローバル変数

int GlobalCounter = 0;
  • 全翻訳単位から自由にアクセス可能
  • プログラム中ずっと存在する共有状態
  • 誰がいつ変更するか曖昧になりやすい

1-2. static変数

ファイルスコープ static(内部リンケージ)

static int FileLocalCounter = 0;
  • 定義したソースファイル内のみ有効
  • 他ファイルからは参照不能

関数スコープ static

void func() {
    static int callCount = 0;
}
  • 関数が呼ばれ続ける間、状態を保持し続ける
  • 初回呼び出し時に初期化される

2. レビューで確認すべき「危険信号」

2-1. スコープが不必要に広がる

  • 影響範囲が不明瞭になる
  • 変更影響範囲を追えなくなる

2-2. 初期化順序に依存する

  • 静的初期化順序問題(Static Initialization Order Fiasco)
  • 複数グローバル間で依存関係が崩れやすい

2-3. 並列競合が潜在する

  • スレッド間で同時更新されると破綻
  • 並列安全性の考慮が必要

3. レビューアーが重点確認すべき観点

観点 確認内容
スコープの適切さ 本当にその範囲で共有する必要があるか?
初期化依存 他のstaticに依存していないか?
責務分離 状態保持責任が整理されているか?
並列安全性 スレッドセーフか?競合の危険はないか?
可視性制御 内部リンケージで閉じ込められているか?

4. 良い実装例:インスタンス責務に閉じる設計

#pragma once

#include <string>

class ApiRequestStatistics {
public:
    void incrementSuccess() { ++successCount; }
    void incrementFailure() { ++failureCount; }
    std::string summary() const;

private:
    int successCount = 0;
    int failureCount = 0;
};
  • インスタンスごとの状態で分離
  • グローバル共有を回避
  • テストや保守が容易

5. 良くない実装例:レビュー指摘パターン

ケース1:無制限グローバル変数

int GlobalRequestCount = 0;
@Reviewer
グローバル変数は避けてください。責務を持つクラス側に状態を移動しましょう。
  • どのコードでも自由に変更可能
  • 変更履歴や影響範囲が追えなくなる

ケース2:静的初期化順序依存の危険

#include <string>

class Logger;
extern Logger GlobalLogger;

class Config {
public:
    Config() {
        GlobalLogger.log("Config initialized");
    }
};
@Reviewer
static初期化順序依存が発生しています。GlobalLoggerが先に初期化されている保証がありません。
  • 順序バグがプラットフォーム依存で発現しやすい

ケース3:関数内staticの無自覚な状態保持

int getGlobalCounter() {
    static int counter = 0;
    return ++counter;
}
@Reviewer
関数内staticで状態保持すると責務が不明確になります。専用の状態管理クラスを切り出しましょう。

6. 改善パターン:責務分離と状態管理の設計

#pragma once

class RequestCounter {
public:
    void increment() { ++counter; }
    int current() const { return counter; }
private:
    int counter = 0;
};
  • 状態保持は専用クラスへ隔離
  • グローバル依存を完全排除
  • 並列化・テスト時も安全性確保しやすい

7. PlantUMLで整理する責務の切り分け

UML Diagram

8. CI統制と自動検出の工夫

clang-tidyを活用

Checks: '-*,cppcoreguidelines-avoid-non-const-global-variables'
  • グローバル変数混入を静的解析で検出

PRレビュー文化として浸透させる

  • 状態保持は専用クラスへ寄せる文化を定着

9. チェックリスト

項目 チェック内容
グローバル禁止 共有状態を不用意に広げていないか
static活用 内部リンケージで責務内に留められているか
初期化順序 順序依存バグを回避できているか
責務整理 状態保持クラスが存在するか
並列考慮 スレッド間競合を防げているか
CI支援 静的解析で検出ルールが整備されているか

10. まとめ

グローバル変数・static変数レビューは「状態責務の境界線」を読む訓練になる。

  • 状態は誰が管理するべきか?
  • どの範囲まで見える必要があるか?
  • 変更影響を局所化できるか?

レビューアーは「影響範囲の広さ」に常に注意を向けると、設計読み取り力が自然に育っていきます。