はじめに

Pythonのdictは非常に柔軟で強力な構造であり、多くの開発者が日常的に使用している。
だがその柔軟性ゆえに、以下のようなレビュー対象となる構造的な問題を含むことがある:

  • 辞書のキー競合による意図しない上書き
  • 副作用を伴うupdate処理
  • マージ処理の責務の曖昧化
  • マージ方針が暗黙で、仕様の読解が困難

レビューアーは、単なる記法や動作確認だけでなく、構造として安全かつ明示的かを判断し、設計意図の補完を行う立場にある。

典型例:マージの副作用を伴うupdate()

updateによる破壊的変更
def merge_options(defaults, overrides):
    defaults.update(overrides)
    return defaults
Comment
@Reviewer: `update()`は元のdictを破壊的に変更するため、副作用を引き起こす可能性があります。呼び出し元でdefaultが再利用される設計か確認が必要です。

このような構造では、元データが意図せず変更されることで後続処理が破壊されるリスクがある。

安全な構造への改善案:コピー+マージ

安全なマージ
def merge_options(defaults, overrides):
    merged = defaults.copy()
    merged.update(overrides)
    return merged

このように、元のdictを保護した上で明示的に上書きの対象を制御することで、構造的に安全な設計となる。

Python 3.9以降の | 演算子によるマージ

dictのマージ構文(3.9+)
merged = defaults | overrides

利便性が高い一方で、上書きルールがコード上から読みにくいというデメリットもある。

Comment
@Reviewer: `|`演算子によるdictマージは直感的である一方、キー競合時の方針が不明確になりやすいため、用途や影響範囲を明示してください。

マージ構造の違い

UML Diagram
UML Diagram

破壊的な変更かどうかは、構造図で明示されることでレビュー判断が容易になる

よくあるレビュー指摘パターンとチェック観点

パターン 指摘観点 構造的提案
dict.update()で破壊的に変更 元dictの使い回しが不明 copy()update()に分離
`defaults = new_config` 演算子の意味と挙動が不透明
dict(**a, **b)での競合無制御 同一キーの競合時の上書きが暗黙 merge_with_conflict_check()導入など
キー存在確認せずにd[k] = v 上書きか新規追加かが読めない if key in d: 構文やgetでの明示
configなどのdictを直接再代入 どこで何が変更されたかトレース不能 一時変数・ログ付き操作で責務分離

dictマージ方針の明示性を指摘

マージ方針の曖昧なコード
def build_config(base, overrides):
    return base | overrides
Comment
@Reviewer: `base | overrides` は処理としては明確ですが、どちらが優先されるか、競合キーがどの程度あるかなど仕様が見えません。用途に応じたマージルールの明示が必要です。

より安全な設計:衝突検出付きマージ処理

競合検出マージ
def safe_merge(a, b):
    overlap = a.keys() & b.keys()
    if overlap:
        raise ValueError(f"Key conflict: {overlap}")
    return {**a, **b}

このように、マージの責務を関数レベルで定義し、競合発生時の設計判断を構造に組み込むことが、安全で明確なAPI設計につながる。

まとめ:レビューアーがdict操作をどう見るか

dict操作は、Pythonでは日常的な記法であるが、レビューアーにとっては設計と副作用を見極める判断ポイントとなる。

レビュー時に確認すべき観点:

  • 辞書の変更が破壊的か非破壊的か
  • 上書きルールやマージ順が明示されているか
  • マージ方針(競合時の優先順位)が構造で補足可能か
  • 初期設定・上書き設定・最終構成の責務が分離されているか

dictの更新は構文の問題ではなく、設計意図と整合性の問題である。
レビューアーは、構造を読む力をもって、意図と影響の明示性をコードに与えていくことが求められる。