この記事のポイント

  • データ構造を構造定義ではなく設計責任で捉えるレビュー技術を養成
  • 責務分離・契約分離・構造選定・操作抽象のレビュー観点を整理
  • 抽象化失敗の典型パターンをレビューアー視点で早期検出する力を強化

そもそもデータ構造の抽象化とは

単なる型定義ではなく責任の分離を意識した構造設計を意味します。

  • 内部保持形 → vector, map, unordered_map, list, set, array など
  • 外部API契約 → どの責務を誰が持つか整理
  • 抽象化 → 利用者に余計な制約を見せず、安全性と意図の分離を行う
// これは単なる型依存
std::vector<ApiRequestLog> logs;

// これは抽象化の第一歩
class ApiRequestLogRepository { ... };
  • 抽象化とは「利用側に余計な内部事情を意識させない」こと
  • それはレビュー時に最も重要な責務整理ポイント

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

レビューアー視点

データ構造抽象化レビューでは以下の設計責任整理が問われる。

  • 内部保持責任とAPI契約責任の分離
    → 内部構造が外部APIに漏れていないか

  • スケーラビリティ保証責任
    → 構造選定が件数増加に対応できる設計か

  • 整合性保証責任
    → データ不整合・重複・ライフサイクルの保証範囲が明確か

  • 更新責任分離
    → 読み取り系と更新系の混在防止

  • 操作抽象責任
    → 単なるcontainer暴露型APIを防止できているか

開発者視点

  • vector/mapを直接API型にしてしまう
  • 生のset/map/list型をパラメータに露出
  • スケーラビリティ負荷試算せず構造採用
  • 保持責任の契約整理放置
  • 重複排除責任を呼出側に委譲

レビューアーはこれら「構造依存設計」を早期発見する役割を担います。

良い実装例

ユースケース:APIリクエストログ管理の抽象化

良い実装例:構造責任カプセル化
#include <vector>
#include <string>
#include <unordered_map>

struct ApiRequestLog {
    std::string requestId;
    int responseCode;
};

class ApiRequestLogRepository {
public:
    void add(const ApiRequestLog& log) {
        logs_.emplace_back(log);
        index_[log.requestId] = &logs_.back();
    }

    const ApiRequestLog* findById(const std::string& id) const {
        auto it = index_.find(id);
        return (it != index_.end()) ? it->second : nullptr;
    }

private:
    std::vector<ApiRequestLog> logs_;
    std::unordered_map<std::string, ApiRequestLog*> index_;
};
  • 内部保持はvector+unordered_map複合設計
  • API契約は検索APIのみ露出
  • 呼出側が内部構造を意識不要
  • スケーラビリティ保証内包

レビュー観点

  • 保持構造がAPI契約に露出していないか
  • インデックス構造採用責任が整理されているか
  • 重複排除責任がAPI側に分離されているか
  • APIは構造提供ではなく操作提供になっているか

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

以下はvectorそのまま外部契約に流出している例。

構造露出例
std::vector<ApiRequestLog> getLogs();
@Reviewer
内部構造が契約に直結し変更困難です。操作抽象へ設計責任分離してください。

問題点

  • vectorに固定化
  • インデックス採用困難化
  • スケーラビリティ改善障害要因化

改善例

修正例:API操作抽象へ昇格
const ApiRequestLog* findById(const std::string& id);

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

次はunordered_mapそのものをAPIで露出している例。

内部辞書露出例
const std::unordered_map<std::string, ApiRequestLog>& getLogMap();
@Reviewer
保持構造が完全露出し自由変更不能になります。契約責任整理が必要です。

問題点

  • 保持戦略が契約硬直化
  • 利用側操作無法地帯化

改善例

修正例:API契約責任分離
const ApiRequestLog* findById(const std::string& id);

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

次は責任混在で検索条件責任が呼出側に委譲されている例。

条件責任放棄例
std::vector<ApiRequestLog> filter(std::function<bool(const ApiRequestLog&)> pred);
@Reviewer
条件定義はAPI契約で整理し、呼出側に過剰責任委譲しないでください。

問題点

  • 呼出側に責務過負荷
  • API設計汎用性が表面化過剰

改善例

修正例:用途専用契約分離
std::vector<ApiRequestLog> findErrors();
std::vector<ApiRequestLog> findByDateRange(time_t from, time_t to);
  • 汎用filterは設計責任不明確化を誘発

データ構造抽象化パターン整理

パターンA:完全保持責任API内部化

class Repository {
public:
    void add(...);
    const Log* findById(...);
};
  • 呼出側は構造非意識
  • 拡張・構造変更容易

パターンB:保持責任呼出側委譲

class Repository {
public:
    void applyIndex(const std::unordered_map<...>&);
};
  • 高度最適化・システム都合で限定使用

内部構造適用早見表

件数 推奨内部構造
少数 vector
一意ID索引 unordered_map
順序保証要 map
頻繁挿入削除 list
範囲照合 set/map
大規模高速検索 複合構造設計

PlantUMLで設計責任整理

UML Diagram

観点チェックリスト

実務レビューFAQ

Q1. vectorそのまま返却が駄目なの?
→ 将来構造変更不能となる。抽象APIへ分離が安全。

Q2. unordered_map返却は設計的にNG?
→ 原則NG。内部保持戦略が契約に縛られてしまう。

Q3. filter関数型APIは便利では?
→ 責任整理困難化が危険。用途専用API分離がレビュー上安全。

Q4. 件数規模いつmap導入?
→ 数千件規模超えたら要検討。アクセス頻度次第。

Q5. データ構造は早期抽象化しすぎない方が良い?
責任整理=早期設計。構造抽象は初期化段階から設計文化化が理想。

まとめ

データ構造抽象化レビューは設計責任とAPI契約整理の基礎訓練である。
レビューアーは

  • 内部構造隠蔽
  • API契約安定化
  • 責任分離設計
  • スケーラビリティ先読み

を読み解き、「構造は実装詳細、契約は抽象化責任」を徹底できるレビュー技術を育成する必要がある。
レビューアーが構造抽象レビューに強くなると設計力は指数関数的に向上する。