9月1日、Evil Martiansが「10 anti-AI slop moves for frontend projects going faster than humans can review」と題した記事を公開した。AIが生成するフロントエンドコードの品質劣化を防ぐための10の具体的な対策について詳しく紹介されている。
AIを使えば、1週間で1万行のコードを生成できる。かつてそれはチームの仕事だった。チームには「なぜそうなっているか」を知る人間がいた。今の「共著者」はそれを持たない。昨日合意したことを覚えておらず、本番障害の責任も負わず、6ヶ月後に決断の理由を説明しに来ることもない。その責任は依然として人間側にある。ただしコードベースは何倍もの速度で膨らんでいる。
こうした状況を指す言葉として、元記事では「AI slop」というキーワードが使われている。AIが機械的に生成する低品質なコード、つまり一見それらしく見えるが文脈を理解せず、既存のアーキテクチャや暗黙のルールを無視した粗雑なアウトプットを指す表現だ。
Evil Martiansが提案する対策は3つの方針に整理される。余分なコードをそもそも書かせない、すでに書かれた問題を検出する、そしてそれをCIで強制する。以下で紹介する10の施策は、この3つの方針のいずれかに属している。
①最重要:OpenAPIでAPIフィールドの「だいたい合ってる」を潰す
【余分なコードをそもそも書かせない】
AIコードの最も典型的な欠陥は「存在しないフィールド」だ。モデルはコンテキストからデータの形を推測する。近くにuserがあればuser.fullNameと書く。バックエンドが実際に返すのはfirstNameとlastNameなのに。そしてTypeScriptはコンパイルを通してしまう。
Stack Overflow 2025 Developer Surveyでは、回答者の66%がAIツールへの最大の不満として「だいたい合っているが完全ではない」を挙げた。これは他の選択肢を大きく引き離してトップだった。
対策はOpenAPIスペックをシングルソースオブトゥルースにすることだ。Hey APIはスペックから型、クライアント、Zodスキーマを生成する。スペックに存在しないuser.fullNameはコンパイルエラーになる。Zodはランタイムの第二の門として機能する(型はコンパイル時にしか存在せず、APIレスポンスはランタイムに届くため)。
生成されたクライアントはリポジトリに置くが、誰も行単位でレビューする必要はない。スペックから再生成されるので、手動編集は上書きされる。レビュー対象がコード数百行からコントラクトの数十行に圧縮される。
②TypeScriptの厳格モードで「自信満々な仮定」を排除する
【余分なコードをそもそも書かせない】
strict: trueはまず前提として入れる。次にnoUncheckedIndexedAccessを追加する。これがないとTypeScriptはarr[0]の要素が必ず存在すると扱う。有効化すると型が「要素またはundefined」になり、空配列の可能性を無視できなくなる。exactOptionalPropertyTypesも同じ方向で効く。
③リンターをAIの「癖フィルター」として使う
【余分なコードをそもそも書かせない/問題を検出する】
リンター設定はスタイルガイドではなく、既知のデフォルト値を持つ特定の書き手に向けたフィルターになった。特に効くルールは以下だ。
- **sonarjs**:認知的複雑度、同一ブランチ、文字列リテラルの重複、ネストした制御フローを検出。AIのコードは行単位では美しく読めるが、ファイルレベルで誰も意図しない構造が積み重なる。
- react-you-might-not-need-an-effect:AIが最も頻繁にやる「不要なuseEffect」を検出。状態同期や派生値の計算にuseEffectを使うと余分なレンダーと状態のズレが増える。
- **@vitest/eslint-plugin**:アサーションのないテスト(
expect-expect)や、うっかり残った.onlyを検出。ただしアサーションが本当に意味のある検証をしているかどうかは後述のミューテーションテストの仕事。 - **jsx-a11y**:AIが「見た目が正しい」に最適化するとき、アクセシビリティは最初に切り捨てられる。
④アーキテクチャ境界を先に宣言する
【余分なコードをそもそも書かせない】
eslint-plugin-boundariesやdependency-cruiserでレイヤー間の依存方向を宣言する。
AIはセッションをまたいでアーキテクチャを記憶しない。制約がなければGitHubの平均的なリポジトリに近いパターンを再現し始める。「一つひとつは合理的に見えるimport」がアーキテクチャを少しずつ侵食していく。
ルール違反時のエラーメッセージには「何が悪いか」だけでなく「依存関係をどこに置くべきか」を含める。これはエンジニアにもエージェントにも同様に有効だ。
⑤繰り返すミスをリンタールール化する
【余分なコードをそもそも書かせない/強制する】
カスタムルールを書くコストは激減した。かつてはAST解析の知識と半日の作業が必要だった。今はAIを使えば10分で動くルールができる。
ルールが存在すべき理由は2種類ある。AIが外から持ち込むデフォルトを打ち消す「行動フィルター」と、実際に出荷したバグを記録する「傷跡」だ。
Evil Martiansの実例:サービスが時間を小数第一位で丸め、フォーマッターがもう一度丸めた結果、26時間15分の合計が26時間18分になった。原因の特定に半日かかった。今はサービスの返り値にMath.roundとtoFixedを書くとエラーになり、理由が表示される。
「そのルールの背後にあるインシデントを名指しできないなら、書くな」というのが判断基準だ。
⑥ミューテーションテストでテストをテストする
【問題を検出する】
カバレッジは「行が実行されたか」しか言わない。Strykerは<を<=に変え、配列を空にし、条件を反転させ、返り値を入れ替える。そしてどの変更をテストが検知できなかったかを示す。
サバイブしたミュータント(検知されなかった変更)が指すのは大抵こういうテストだ:
toHaveBeenCalled()で呼び出しの有無だけ確認し、引数を確認しない- 誰も中身を検証しないまま作ったスナップショット
- 計算ロジックより先にUIの振る舞いをテストし、ビジネスロジックが素通り
⑦デッドコード検出で静かな増殖を防ぐ
【問題を検出する】
Knipはエクスポートされているが使われていないファイル、関数、型を検出する。AIは新しいコードを書くとき既存のコードを探しにいかないため、重複したユーティリティが静かに増殖する。
⑧重複コード検出で「再発明」を可視化する
【問題を検出する】
デッドコードと表裏一体の問題として、AIは既存の実装を参照せず同等の処理を別の場所に書き直すことが多い。重複検出ツールを組み合わせることで、コードベース内の「再発明」を定期的に可視化できる。
⑨AIエージェント向けのルールファイルを整備する
【余分なコードをそもそも書かせない】
.cursor/rulesやCLAUDE.mdといったエージェント向けの指示ファイルに、プロジェクト固有のアーキテクチャ判断や禁止パターンを明文化する。セッションをまたいで記憶を持たないAIに対して、毎回のコンテキストとして与えることで、前述のカスタムリンタールールと二重の防衛線を構成できる。
⑩CIで全てを強制する
【強制する】
最後の一手はこれら全てをCIで強制すること。スキップできるチェックはチェックではない。ローカルのpre-commitフックではなく、PRをブロックするCIゲートに置く。型チェック、リンター、ミューテーションスコアのしきい値、Knipの未使用エクスポート検出——これらが全てパスしなければマージできない状態にして初めて、防衛ラインとして機能する。
詳細は10 anti-AI slop moves for frontend projects going faster than humans can reviewを参照していただきたい。




