1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIで爆速実装→なぜか遅くなる?「AIコードの匂い」カタログ5選(直し方まで)

1
Posted at

生成AIで実装スピードは上がりました。(自分は入社時からAIを使っているので前時代のことはよくわかりませんが...)
なのに、体感としては

  • 実装:速い(PRができる)
  • でも レビュー:指摘が多い
  • さらに デバッグ:時間が溶ける
  • 結果、トータルは遅い

実際に、AI支援の変更はPR上の問題(issues)が増えやすいというレポートも出ています。 (CodeRabbit)
また、LLM生成コードは参照実装よりコードスメルの発生が増える、という研究報告もあります。 (arXiv)

この記事はその「遅くなる原因」を、“AIっぽい匂い”として嗅ぎ分けて直すための自分の経験をまとめたカタログです!

対象読者

  • いわゆる vibe coding で実装を進めている(Cursor / Copilot / Claude Code 等)
  • 「動くけど、PRで揉める」「直すのが怖い」が増えてきた
  • 既存コードへの“馴染ませ方”を言語化したい

なぜAIだと“匂い”が増えるのか(ざっくり)

生成AIは「今この場で動く解」を出すのが得意です。
でも開発のボトルネックは、だんだん “書く”より“読む/直す/揃える” に寄ります。

  • 読む:理解できないとレビューが止まる
  • 直す:影響範囲が読めないとデバッグが沼る
  • 揃える:既存の流儀から浮くと、説明コストが増える

つまり「生成は速い」が「理解は別ゲーム」。
このギャップが“匂い”として現れます。

AIコードの匂いカタログ(厳選5本)+直し方

それぞれ「サイン / 何が困る / 直し方(具体)」でまとめます。

1) 既存コードとの一貫性が壊れている

サイン

  • 命名が突然別文化(UserManager が急に生える、fetch/get/load が混在)
  • エラーの返し方がそのファイルだけ違う(例外派 vs Result派)
  • 置き場所・責務の切り方がその変更だけ浮いてる

何が困る

  • 読む側が「方言の翻訳」を強いられてレビューが遅くなる
  • “なぜこの形?”の説明が必要になり、PRが会話地獄になる

直し方(具体)

(A) まず既存の「型」を観察して寄せる

  • 命名:同じ責務のクラス/関数を3つ探して、単語を揃える
  • 例外:同じ層の関数が「throwしてるか / Result返してるか」を揃える
  • ファイル配置:同種の処理がどこにあるかに寄せる

(B) “差”には理由をつける

  • 既存と違う形を採るなら、PR本文に「なぜ必要か」を短く書く
    (理由のない差分は、匂い扱いされがち)

この「一貫性を守る」は、AI時代ほど効きます。AIの出力はそれっぽくても、リポジトリの流儀までは自動で揃いきらないからです。

2) レイヤー(MVC等)の境界が溶ける/外部依存が染み出す

サイン

  • ControllerからDB/外部API直叩き、ViewModelがドメインルール持つ
  • ドメイン層にORM/HTTPの型が露出する
  • 「同じルール」が複数層にコピペされる(バリデーション地獄)

何が困る

  • 仕様変更が“増幅”する(直す箇所が芋づる式に増える)
  • 影響範囲が読めず、デバッグで迷子になる

直し方(具体)

(A) “知識の置き場所”を固定する(情報隠蔽の発想)

  • 「そのルール/形式を誰が知るべきか?」を決めて、そこに閉じ込める
    例:HTTPの詳細は境界(Controller/Adapter)まで、ドメインに持ち込まない

(B) 境界に“変換レイヤー”を置く

  • 外部API → 自前DTO → ドメインモデル のように、型の越境を1箇所に集約
  • ドメイン側は「自分の言葉(型)」だけで喋らせる

この発想は「モジュールは深く(インターフェースは薄く)」にも繋がります。 (ソフトウェア工学の現代的アプローチ)

3) 分岐迷路(深いネスト / if-swich増殖 / フラグ引数)が同時発生する

サイン

  • if が深い、ハッピーパスが見えない
  • switch が肥大化し、追加のたびに同じ塊を編集
  • render(true) みたいなフラグ引数で内部が二股三股

何が困る

  • 目視で追える条件の限界を超える(レビュー・デバッグが止まる)
  • “追加が怖い”状態になり、保守が詰む

直し方(具体)

(A) ネストはガード節で平坦化

  • 「例外/早期returnを先に並べて、通常系を一番見える場所に置く」
    これは定番のリファクタリングとして整理されています。 (リファクタリング guru)

例(擬似コード):

// before
function price(user) {
  if (user) {
    if (user.plan) {
      if (user.plan.type === "pro") { ... }
      else { ... }
    } else { ... }
  } else { ... }
}

// after(ガード節)
function price(user) {
  if (!user) return 0
  if (!user.plan) return 0

  if (user.plan.type === "pro") return proPrice(user)
  return basicPrice(user)
}

(B) switch/if増殖は“分岐の理由”を型に移す

(C) フラグ引数は関数を分けて“意図を名前へ”

  • render(true)renderForSuite() / renderForSingle() のように
    「呼び出し側が意味を読める」形にする

(D) 引数が増えるなら “概念の塊” にまとめる

4) 追跡が長い(Trainwreck / パススルー / Shallow Module)

サイン

  • これ系が増える:

    • a.getB().getC().getD()
    • 「AがBを呼ぶだけ」「引数を渡すだけ」の中継が多い
  • 変更の影響を見るために、ファイル間を往復する回数が増える

何が困る

  • 読むコストが増えるだけで、理解が進まない(=レビューが重い)
  • 内部構造に依存して壊れやすい(=デバッグがしんどい)

直し方(具体)

(A) Tell, Don’t Ask:データを聞いて外で処理しない

  • 「値を取り出して組み立てる」のではなく、「オブジェクトにやらせる」 (martinfowler.com)
// before(聞いて外で処理)
const path = ctx.getOptions().getScratchDir().getAbsolutePath()

// after(命じる)
const path = ctx.scratchPath()

(B) デメテルの法則:知りすぎない

  • メソッドチェーン(Trainwreck)を匂いとして扱う考え方 (Qiita)

(C) “浅い層”を減らして、モジュールを深くする

5) 意味が型に乗らない(Primitive Obsession / 汎用コンテナ / マジック値)

サイン

  • string / int に意味が詰め込まれている(単位・制約が不明)
  • Map<String, String>Pair が返ってきて get("x") 祭り
  • -1null が“特別な意味”を持っている

何が困る

  • 取り違えがコンパイルもテストもすり抜ける
  • 呼び出し側が暗黙ルールを覚える必要があり、レビューが荒れる

直し方(具体)

(A) 値オブジェクト化:意味・制約・単位を閉じ込める

// before
function charge(amount: number) { ... } // 円?ドル?税抜?

// after
class Money { constructor(readonly yen: number) { ... } }
function charge(amount: Money) { ... }

(B) 汎用コンテナは “名前のある戻り値” にする

  • Pair / Map を返すより、専用DTOでフィールド名を与える
    → 読む側が「意味」を推測しなくて済む

(C) マジック値は型で表す

  • Optional / Result 型、もしくは例外方針の統一(※項目1とセットで効く)

まとめ:AIの出力は「原稿」。速さを成果に変えるのは「編集」

vibe coding が強いのは、原稿(動くコード)を出す速度です。
でもチーム開発で効くのは 読めること・揃っていること・境界が守られていること

今回の5つは、AIコードの違和感が出やすい場所を“圧縮”したものです。

  • 一貫性(既存の文脈に馴染ませる)
  • 境界(レイヤーを守る)
  • 分岐(迷路にしない)
  • 追跡(往復を減らす)
  • 型(意味を閉じ込める)

参考

  • CodeRabbit「AI vs Human code generation report」(AI PRはissuesが増えやすい) (CodeRabbit)
  • LLM生成コードのコードスメル増加を報告する研究 (arXiv)
  • Tell, Don’t Ask(Martin Fowler) (martinfowler.com)
  • Replace Nested Conditional with Guard Clauses / Introduce Parameter Object(Refactoring.Guru) (リファクタリング ガuru)
  • Deep Modules(Ousterhoutの整理の紹介) (ソフトウェア工学の現代的アプローチ)
  • Qiitaで読まれる書き方(導入で読む理由を約束、フォーマットで読むストレスを減らす等) (Qiita)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?