この記事のポイント

  • Counter利用時のゼロ件保証責務をレビューできる
  • 集計初期化責務と出力整形責務を分離設計できる
  • 集計漏れ・暗黙依存・実装事故を防ぐレビュー観点が身につく

そもそもCounterとは

Python標準ライブラリcollections.Counterは、要素の出現回数を自動的にカウントする辞書型です。

from collections import Counter

data = ["apple", "orange", "apple"]
result = Counter(data)
# Counter({'apple': 2, 'orange': 1})

特徴は以下です。

  • 要素出現回数の自動集計
  • 存在しないキーは自動的に0返却
  • 累積処理・頻度計測に最適

初期ゼロ保証の自動性は便利である反面、設計次第で責務崩壊リスクも抱えます。

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

現場で頻発する失敗は以下です。

  • ゼロ保証の過信による集計漏れ
  • 集計責務の曖昧化
  • 出力責務がCounterに依存しすぎる
  • 欠損許容設計ミスの温床化

レビューアーは「ゼロ件保証は集計責務なのか出力整形責務なのか?」を読み取る必要があります。

レビューアー視点

  • 集計責務範囲が整理されているか
  • 出力形式のゼロ保証が明示されているか
  • Counterの自動ゼロ保証を過信していないか
  • 欠損補完処理責務が分離設計されているか
  • 出力側コードの事故リスクが最小化されているか

開発者視点

  • Counterは純粋な集計器として使う
  • ゼロ補完は出力整形責務に分離
  • 欠損初期化ロジックを集計中に混入させない
  • 出力仕様を意識した整形専用処理を用意
  • 欠損保証範囲をレビュー時に明文化

良い実装例

なぜこの実装が良いのか

  • Counterは純粋集計責務のみ担当
  • ゼロ保証は出力整形責務で担当
  • 出力要件に応じた責務分離
  • 欠損補完仕様がコードに明示
# counter_with_zero_filling.py

from collections import Counter

def count_words(words: list[str], known_words: list[str]) -> dict[str, int]:
    counter = Counter(words)
    return {word: counter.get(word, 0) for word in known_words}
補足

「Counterはカウント専用、整形責務は別」が基本設計です。レビューアーは整形責務分離有無を重点確認します。

レビュー観点

  • Counterは純粋に集計専用で使われているか
  • 欠損補完責務が整形層に分離されているか
  • 集計時の初期化責務が曖昧化していないか
  • 出力要件が型・仕様上で保証可能か
  • 欠損保証仕様がレビューで即座に確認できるか

良くない実装例: ケース1(Counter自体に出力仕様依存)

# bad_counter_as_viewmodel.py

from collections import Counter

def count_status(statuses: list[str]) -> Counter:
    counter = Counter(statuses)
    return counter
@Reviewer
Counterオブジェクトをそのまま出力責務に流用しています。整形責務を分離してください。

問題点

  • 出力層でCounter内部仕様依存
  • 欠損保証仕様が曖昧
  • 呼び出し側が型仕様事故リスクを背負う

改善例

# good_counter_as_pure_aggregator.py

def count_status(statuses: list[str], known_statuses: list[str]) -> dict[str, int]:
    counter = Counter(statuses)
    return {status: counter.get(status, 0) for status in known_statuses}

出力仕様はCounter以外の安定型(dict等)で明示化するのが設計原則です。レビューでは型安全性を確認します。

良くない実装例: ケース2(初期値補完を集計中に混入)

# bad_inline_initialization.py

from collections import Counter

def count_categories(categories: list[str], known: list[str]) -> dict[str, int]:
    counter = Counter({key: 0 for key in known})
    counter.update(categories)
    return dict(counter)
@Reviewer
集計前に初期化責務が混入し集計責務が肥大化しています。整形処理に分離してください。

問題点

  • 集計開始前に欠損補完ロジック混在
  • 集計責務が整形責務を内包
  • 再利用性・保守性崩壊

改善例

# good_separated_initialization.py

def count_categories(categories: list[str], known: list[str]) -> dict[str, int]:
    counter = Counter(categories)
    return {key: counter.get(key, 0) for key in known}

集計処理は純粋に「データから出現回数を取る」だけに限定が原則です。レビューではこの責務線引きを明確に読みます。

良くない実装例: ケース3(欠損補完を呼び出し側に放置)

# bad_caller_fill_missing.py

def count_items(items: list[str]) -> dict[str, int]:
    return dict(Counter(items))

# 呼び出し側で補完
counts = count_items(["A", "B", "B"])
for key in ["A", "B", "C"]:
    count = counts.get(key, 0)
@Reviewer
欠損補完責務が呼び出し側へ漏洩しています。API提供側で整形責務を吸収してください。

問題点

  • 呼び出し契約に欠損補完要求が漏洩
  • API利用者の認知負荷増大
  • 再利用障害

改善例

# good_api_level_filling.py

def count_items(items: list[str], known_keys: list[str]) -> dict[str, int]:
    counter = Counter(items)
    return {key: counter.get(key, 0) for key in known_keys}

API利用者は欠損補完を意識不要が理想設計です。レビューではAPI契約の責務線引きを確認します。

良くない実装例: ケース4(defaultdict代替で設計事故)

# bad_counter_vs_defaultdict.py

from collections import defaultdict

def count_words(words: list[str]) -> dict[str, int]:
    counter = defaultdict(int)
    for word in words:
        counter[word] += 1
    return dict(counter)
@Reviewer
Counterが提供する集計専用APIを使わず汎用defaultdictを利用しています。集計責務にはCounterを優先してください。

問題点

  • 集計器としての責務明示性消失
  • 汎用defaultdictで意図が読めなくなる
  • 再利用設計困難化

改善例

# good_use_counter.py

from collections import Counter

def count_words(words: list[str]) -> dict[str, int]:
    return dict(Counter(words))

集計=Counter、状態保持=dict、初期化責務=整形層という責務線引きがレビュー基本方針です。

観点チェックリスト

まとめ

Counter設計レビューは「集計責務 vs 出力保証責務」の線引き訓練です。
レビューアーは常に

  • Counterが集計専用になっているか?
  • 欠損保証は出力整形に分離されているか?
  • 呼び出し契約が自然に閉じているか?

を読み取り、「設計事故の温床となる責務混在を早期遮断する」レビュー習慣を徹底していきます。
Counterレビューは集計系設計教育の極めて有効な教材領域です。