CTS-KB

Python ジョブ群編 — 握り潰された except は 3 箇所ではなく 8 箇所あった。スタブ・enum 三重定義の一掃【実践編 第8章】

⏱ 約 7 分で読めます
#ステアリング駆動開発#Python#技術的負債#例外処理#リファクタリング#LLM#バッチ処理

📖 はじめに — Python ジョブは「静かな負債」の巣になりやすい

CTS-EC のバッチ処理は 5 つの Python ジョブ(AI マッピング・埋め込み生成など)が担う。第4章で SAGA 側の契約は固めたが、Python コードの内側には別種の負債が残っていた — 動いているように見えて、実は黙っている類の負債だ。

  • NotImplementedError を投げるスタブや「未実装」print の残置
  • except (KeyError, IndexError, TypeError): pass による例外の握り潰し
  • 同名 enum の三重定義(しかも微妙に振る舞いが違う)

どれも AI コーディングが量産しがちなパターンである。「とりあえず動かす」ための仮実装、「エラーで止まらないように」の防御的 pass、「隣のジョブからコピペ」した enum。プロジェクトには「stub/TODO を残さない」という明文ルールがあったが、ルールだけでは増幅に勝てない — 本章はその違反を棚卸しし、機械ガードで再発を封じるまでの実録だ。

🗑️ スタブ一掃 — 「v2 を残す」計画を実態調査が覆した

最初の標的は、正規化マッピング処理に二世代併存していたスタブだった。v1 は NotImplementedError を投げ、v2 は部分実装。当初の設計は「v1 を消し、v2 は allowlist で管理しつつ残す」だった。

着手前の Phase 0 実態調査が、この計画を覆す。CLI の本番経路は別実装を通っており、v1 も v2 も実は dead code だと判明したのだ。決定は「両方削除」に変わり、当初想定していた allowlist 登録も不要になった。未実装の status コマンドも同様に撤去。

第4章の database-per-worker 撤回と同じ、型 4(過剰設計を実態調査で捨てる)の再演だ。「スタブをどう管理するか」を精緻に設計する前に、そのスタブは本当に使われているのかを確かめる。dead code の管理設計ほど無駄なものはない。

🤫 本章の目玉 — except-pass は 3 箇所ではなく 8 箇所あった

負債台帳に載っていた例外握り潰しは、PMM ジョブの LLM レスポンス parse ヘルパー 3 箇所だった。LLM の応答 JSON から値を取り出す際、構造が想定と違うと except (KeyError, IndexError, TypeError): pass黙って握り潰す — マッピング結果が静かに欠落し、誰も気づかないという最悪の形だ。

是正そのものは手堅い。戻り値の仕様は一切変えず、握り潰しの箇所に構造化ログ(logger.warning + コンテキスト情報)を差し込む「無破壊是正」で、失敗が観測可能になった。

事件はガードを敷く段で起きた。再発防止の grep ガードを追加する前に、検出パターンでコードベース全体のゼロベースラインを確認したところ — 同一のコピペ masking パターンが、台帳に載っていない 3 ファイル・5 箇所(別ジョブ 2 つ)から出てきた。LLM parse ヘルパーがジョブ間でコピペされ、握り潰しごと増殖していたのだ。

分岐点はここだ。5 箇所を「今回のスコープ外」として allowlist で逃がすこともできた。だが「allowlist で逃がすとガードが無意味化する」というオーナー判断で、対象を全 8 箇所に拡張し、同一の構造化ログ処理を適用してから、ガードを allowlist ゼロ行で稼働させた。

第3章の「allowlist で逃がすな」、第7章の隠れコピペ 1,124 行と、完全に同じ型(型 2: ゼロベースライン確認が兄弟負債を炙り出す)である。機械ガードの本当の価値は、敷いた後の防御より、敷く前の全量調査が強制されることにあるのかもしれない。

🔧 ガードの細部 — 複数行 grep・コメント誤検出との戦い

このガード、検出パターンの設計が地味に渋いので記録しておく。ドキュメントレビューと実装で詰めた論点だ。

  • 検出は 3 つの精密パターンに限定: ①raise NotImplementedErrorexcept …: の直後が pass のみ ③「未実装 / not implemented」を含む print。曖昧な TODO 検索にしない(誤検出でゲートが狼少年化するのを防ぐ)
  • except…pass は複数行構文なので単一行 grep では検出できない — ドキュメントレビュアーの MUST 指摘。grep -Pzo(NUL 区切りの複数行マッチ)または Python AST 方式を明記し、単一行 grep の流用を禁止
  • 正当な保留はトリガー語を避けた構造化マーカーで書く: 意図的に後回しにした処理は logger.info("gmc_sync_db_writeback_deferred") のような構造化ログ + 説明コメントとし、コメントからも literal トリガー語(NotImplementedError 等)を除去して誤検出を回避

「ガードを書く」は 1 行の grep では済まない。何を検出し、何を検出しないかの境界設計こそが本体で、そこにはレビューの往復が要る。

🧬 enum 三重定義 — 「単純重複」と思ったら振る舞いが違った

最後の標的は、Batch ジョブのステータス enum が 1 つのジョブ内に 3 定義あった問題だ。統合は単純作業に見えたが、実装フェーズで重要な発見があった — 3 つは単純コピペではなく、メンバー構成が分岐していた(片方にしかない UNKNOWN / PAUSED)。盲目的にどれか 1 つへ寄せると、残り 2 つが処理していた状態が落ちる。統合は和集合 7 メンバーで行い、差異はレビューで明示確認した。

スコープの裁きも記録に値する。「せっかくなら 5 ジョブ横断の共有ライブラリに」という案はオーナー判断で却下され、集約はジョブ内のみ(3 → 1) に留めた。ジョブは独立パッケージであり、安易な共有 lib はジョブ間の結合という新しい負債を作る。負債解消のスコープを膨らませないのも規律だ。

さらに push 前レビューで、隣のジョブに SAGA 同型性契約の C-10(API ステータスの独自 enum 変換禁止)違反が本番経路に残っていることまで発見されたが、これも独立パッケージ境界を尊重して後続の負債起票へ回した。見つけたものを全部その場で直さない — 直せるものと直すべき単位を分ける。

🧾 まとめ

  • スタブの「管理方法」を設計する前に、そのコードが生きているか実態調査する。dead なら管理でなく削除
  • 例外握り潰しの是正は戻り値不変 + 構造化ログの無破壊是正が安全。そしてガードのゼロベースライン確認が、台帳の 3 箇所を 8 箇所に正す — allowlist で逃がすとガードは無意味化する
  • 機械ガードは検出境界の設計が本体。複数行構文・コメント誤検出・正当な保留の書き方まで詰めて初めて常設できる
  • 同名 enum の統合は「振る舞い差分の確認 → 和集合」。共有 lib 化の誘惑はパッケージ境界の尊重で裁く

次章は、性能の名の下に導入された機能が障害の元凶になった話 — パフォーマンス編へ。

📚 シリーズ記事(ステアリング駆動開発・実践編)

序章

  1. AI 駆動開発が積み上げる技術的負債

第I部: 技術的負債ドメイン別の実録(総点検ガイド + 8 ドメイン・全 9 回)

  1. リファクタリング総点検ガイド — 7 つの観点と進め方
  2. DDD/SOLID/BC 編 — god class 一掃と境界の機械ガード
  3. SAGA 編 — 新アーキテクチャ挑戦と再発ゲート
  4. Atomic Design リファクタ編 — 47 page 新規移植
  5. Storybook × a11y 実機編
  6. テスト品質・網羅性編
  7. Python ジョブ群編(本記事)
  8. パフォーマンス編
  9. セキュリティ編

第II部: 検証エンジン(クロスファミリー検証・全 6 回)

  1. 総論 — マルチ LLM の 2 系統と見取り図
  2. 裏取り編 — +18.1pt 論文の検証
  3. 設計編 — finder/verifier 分業と逆順禁止
  4. TDD×ハーネス編 — テスト保護と三層ゲート
  5. 実装編 — Claude Code の中から Codex を動かす
  6. モデル戦略編 — ティア割当と「買うか組むか」

終章

  1. クロスファミリー検証の最終型 —「正しく作る」から「自分で正しさを測り直す」へ — 検証エンジンに「計測」の層をはめ、自己修正ループを閉じる

番外編

  1. CI テスト 29 分 → 5.6 分 — 検証エンジンの平時運用実録 — 実測が有力仮説を殺し、退行前より速くなった 1 日
  2. GPT-5.6 sol 切替の当日実録 — もう一つのデフォルト追従 — CLI 更新が黙って替える finder と、当日中の再計測
  3. 遊休 90% の枠に仕事を振る — Codex 上流調査と verifier バッチ化 — 逆順禁止の境界を ADR で確定し、読む仕事を遊休枠へ移した 1 日
  4. ハーネスが単一プロジェクトを卒業する — プラグイン化と二層配布 — 4 プロジェクト展開への切り出しと、version + SHA ピンによる非強制追従
  5. 69分で5リリース — Opus 5 当日対応が暴いた共通ハーネスの死角 — 新モデル対応を起点に、配布・文書・CI の回帰を二消費者で検出した 69 分

関連記事