# AI Antipattern 検出基準

## 仮定の検証

AIはしばしば仮定を行う。それを検証する。

| 確認項目 | 質問 |
|---------|------|
| 要件 | 実装は実際に要求されたものと一致しているか? |
| コンテキスト | 既存のコードベースの規則に合っているか? |
| ドメイン | ビジネスルールは正しく理解されているか? |
| エッジケース | AIは現実的なエッジケースを考慮したか? |

危険信号:
- 実装が異なる質問に答えているように見える
- コードベースの他の場所にないパターンを使用
- 特定の問題に対して過度に汎用的な解決策

## もっともらしいが間違っている検出

AIは正しく見えるが間違っているコードを生成する。

| パターン | 例 |
|---------|-----|
| 構文は正しいが意味が間違っている | 形式をチェックするがビジネスルールを見落とすバリデーション |
| 幻覚API | 使用しているライブラリバージョンに存在しないメソッドの呼び出し |
| 古いパターン | 学習データからの非推奨アプローチの使用 |
| 過剰エンジニアリング | タスクに不要な抽象化レイヤーの追加 |
| 過小エンジニアリング | 現実的なシナリオのエラーハンドリングの欠如 |
| 配線忘れ | 機構は実装されているが、エントリポイントから渡されていない |

検証アプローチ:
1. このコードは実際にコンパイル/実行できるか?
2. インポートされたモジュール/関数は存在するか?
3. このライブラリバージョンでAPIは正しく使用されているか?
4. 新しいパラメータ/フィールドが追加された場合、呼び出し元から実際に渡されているか?
   - AIは個々のファイル内では正しく実装するが、ファイル横断の結合を忘れがち
   - `options.xxx ?? fallback` で常にフォールバックが使われていないか grep で確認

## コピペパターン検出

AIは同じパターンを、間違いも含めて繰り返すことが多い。

| 確認項目 | アクション |
|---------|----------|
| 繰り返される危険なパターン | 複数の場所で同じ脆弱性 |
| 一貫性のない実装 | ファイル間で異なる方法で実装された同じロジック |
| ボイラープレートの爆発 | 抽象化できる不要な繰り返し |

## 冗長な条件分岐パターン検出

AIは条件分岐で同一関数を引数の差異のみで呼び分けるコードを生成しがちである。

| パターン | 例 | 判定 |
|---------|-----|------|
| 引数の有無のみの分岐 | `if (x) f(a, b, c) else f(a, b)` | REJECT |
| オプション有無の分岐 | `if (x) f(a, {opt: x}) else f(a)` | REJECT |
| 戻り値を使わない冗長な else | `if (x) { f(a, x); return; } f(a);` | REJECT |

```typescript
// REJECT - 両ブランチが同一関数を呼び出し、第3引数の有無のみが異なる
if (options.format !== undefined) {
  await processFile(input, output, { format: options.format });
} else {
  await processFile(input, output);
}

// OK - 三項演算子で統一
const formatOpt = options.format !== undefined ? { format: options.format } : undefined;
await processFile(input, output, formatOpt);
```

検証アプローチ:
1. if/else ブロックで同一関数を呼び出している箇所を探す
2. 差異がオプション引数の有無のみなら、三項演算子やスプレッド構文で統一
3. 分岐ごとに異なる前処理がある場合は、変数に結果を格納してから単一呼び出しに統一

## コールバック + 外部変数キャプチャの濫用

AIは戻り値で返せるデータを、コールバック関数と外部変数のキャプチャで実装しがちである。

| パターン | 例 | 判定 |
|---------|-----|------|
| コールバックで外部変数に代入 | `let result; await f(x => { result = x })` | REJECT |
| イベントハンドラ経由で値を取得 | `emitter.on('data', d => { captured = d })` で同期的に値を取得 | REJECT |
| 複数のコールバックで外部状態を組み立て | `forEach(item => {外部Map.set(...) })` で結果を構築 | REJECT |

```typescript
// REJECT - コールバックで外部変数をキャプチャ
let selectedMode: string | undefined;
await promptUser(choices, (choice) => {
  selectedMode = choice;
});
return selectedMode;

// OK - 戻り値で受け取る
const selectedMode = await promptUser(choices);
return selectedMode;
```

検証アプローチ:
1. コールバック関数内で外部スコープの変数に代入している箇所を探す
2. その値が関数の戻り値で返せるか確認
3. 返せる場合は戻り値パターンに書き換えを指摘する

## レビュー指摘への不適切な対応検出

AIはレビュー指摘を「修正」する代わりに、テストやドキュメントで「指摘内容を検証する」コードを追加して対応したつもりになることがある。

| パターン | 例 | 判定 |
|---------|-----|------|
| 指摘の修正ではなくテストの追加 | 「コメント不要」→ コメントの有無を検証するテストを追加 | REJECT |
| 指摘の修正ではなくドキュメントの追加 | 「DRY違反」→ 重複が意図的である旨のドキュメントを追加 | REJECT |
| 指摘と無関係なファイルの変更 | セキュリティ指摘 → 全く別のリファクタリングを実施 | REJECT |

検証アプローチ:
1. 修正差分が指摘の対象ファイル・対象行に対する変更を含んでいるか確認
2. 新規ファイル追加のみで対応している場合、そのファイルが指摘の「修正」ではなく「検証」になっていないか確認
3. テスト追加が指摘への対応の場合、テストが「修正後の正しい動作」を検証しているか（「指摘内容そのもの」を検証していないか）確認

## コンテキスト適合性評価

コードはこの特定のプロジェクトに合っているか?

| 側面 | 検証 |
|------|------|
| 命名規則 | 既存のコードベースのスタイルに一致 |
| エラーハンドリングスタイル | プロジェクトのパターンと一貫性 |
| ログ出力アプローチ | プロジェクトのログ規則を使用 |
| テストスタイル | 既存のテストパターンに一致 |

確認すべき質問:
- このコードベースに精通した開発者ならこう書くか?
- ここに属しているように感じるか?
- プロジェクト規則からの説明のない逸脱はないか?

## インテグレーションパターンの一貫性

同じ種類のAPI接続（REST呼び出し等）がプロジェクト内で異なる方式で実装されていないか確認する。

| パターン | 例 | 判定 |
|---------|-----|------|
| 生成クライアントと手書きクライアントの混在 | A画面はOrval生成フック、B画面はaxiosInstance直接 | REJECT |
| 同じデータ取得パターンの異なる実装 | A画面はuseQuery+axios、B画面は生成フック | REJECT |
| データ型の定義方式の混在 | A画面は生成された型、B画面は手書きの型 | REJECT |

検証アプローチ:
1. 変更差分のAPI呼び出し方式を確認
2. 同じ目的の既存コードがどの方式で書かれているか grep で確認
3. プロジェクトにAPI生成設定（orval.config.ts等）があるか確認
4. 不整合がある場合、プロジェクトの標準パターンへの統一を指摘する

## スコープクリープ検出

AIは過剰に提供する傾向がある。不要な追加をチェック。

| 確認項目 | 問題 |
|---------|------|
| 追加機能 | 要求されていない機能 |
| 早すぎる抽象化 | 単一実装のためのインターフェース/抽象化 |
| 過剰設定 | 設定可能にする必要のないものを設定可能に |
| ゴールドプレーティング | 求められていない「あると良い」追加 |
| 関連変更に見える余計な変更 | 変更箇所の周辺という理由で、要求達成に不要な整理・改名・移動を行う |
| 観測可能な契約のついで変更 | 既存利用者やテストが観測する値を要求外で変更 |
| 不要なLegacy対応 | 明示的な指示がないのに旧値のマッピング・正規化ロジックを追加 |

最良のコードは、問題を解決する最小限のコード。

### 関連変更に見える余計な変更

AIは「触ったファイル内」「近い責務」「作法として自然」という理由で、要求達成に不要な整理を関連変更として正当化しがちである。関連変更かどうかは距離ではなく、要求達成に必要な因果関係で判定する。

| パターン | 判定 |
|---------|------|
| 変更ファイル内という理由だけの改名・移動・責務変更 | REJECT |
| 実装に直接必要ない Props 型名、戻り値構造、公開関数名の変更 | REJECT |
| 既存コメントを「説明コメント」と決めつけて削除する | REJECT |
| テストを通すために、検証済みの既存挙動を削除・緩和する | REJECT |
| フレームワーク作法への改善を、明示要求なしに混ぜる | REJECT |
| 新機能の配線に必要な引数追加・呼び出し元更新 | OK |
| 不要になった旧実装の削除 | OK |

検証アプローチ:
1. タスク開始時点（ベース）からの累積差分全体を取得し、改名、移動、削除、責務変更、テスト期待値変更を列挙する（直近の修正分だけを見ない。過去イテレーションで紛れた無関係変更は最新の修正レポートに現れない）
2. 各変更について「この変更がないと要求を満たせないか」を確認する
3. 「読みやすい」「作法として良い」「ついでに整理」は必要性の根拠にしない
4. 必要性を説明できない変更は、完了前に戻す

### 観測可能な契約のついで変更

AIは「改善」「標準化」「説明性向上」の名目で、要求と無関係な既存契約を変更しがちである。UI文言、アクセシビリティ名、イベント名、戻り値、エラー文言、ログ形式、公開API、型名、ファイル配置、コメント、テストが検証している挙動は観測可能な契約として扱う。

| パターン | 判定 |
|---------|------|
| 要求外の契約変更 | REJECT |
| 既存テストを新しい契約へ追随させるだけの変更 | REJECT |
| 新機能に必要な新しい契約の追加 | OK |
| 既存契約を保ったまま不足情報を追加 | OK |
| 契約変更の理由、影響範囲、移行方針が明示されている | OK |

検証アプローチ:
1. 差分内の文字列、属性、イベント名、戻り値、エラー文言、ログ形式を確認する
2. それぞれが今回の要求に直接必要か確認する
3. テスト期待値が実装変更へ追随しているだけなら、元の契約を維持できないか確認する
4. 契約変更が必要な場合は、理由と影響範囲が説明されているか確認する

Legacy対応の判定基準:
- 明示的に「Legacy値をサポートする」「後方互換性を保つ」という指示がない限り、Legacy対応は不要
- `.transform()` による正規化、`LEGACY_*_MAP` のようなマッピング、`@deprecated` な型定義は追加しない
- 新しい値のみをサポートし、シンプルに保つ

### 関数オブジェクトによる過剰抽象化

AIは少数の具体分岐を、設定配列・関数オブジェクト・汎用ループに置き換えて「拡張しやすく」見せがちである。問題は Strategy ではなく、概念名を与えずに差分をデータへ隠すこと。業務概念として名前が付き、差し替え点が明確になる Strategy は有効である。

| パターン | 例 | 判定 |
|---------|-----|------|
| 1箇所でしか使わない処理設定配列 | `[{ kind, fields, removedFields }]` を loop で処理 | REJECT |
| 削除・副作用・例外条件が設定オブジェクトに隠れる | 設定値を読まないと破壊的変更が分からない | REJECT |
| 分岐ごとの処理が1-3行で完結するのに関数オブジェクト化 | `handlers[type]()` だけが増える | REJECT |
| Strategy が業務概念を表し、実装差し替えの境界を明確にする | `TaxPolicy`, `PaymentMethod`, `RetryStrategy` | OK |
| 分岐数が多く、処理形が同じで、今後も追加される | handler map を検討 | OK |

検証アプローチ:
1. 追加された配列・Map・Strategy・関数オブジェクトの使用箇所を grep する
2. 使用箇所が1つなら、明示分岐の方が読みやすいか確認する
3. 設定オブジェクトに副作用・削除対象・互換処理が隠れていないか確認する
4. ドメイン上の意味が分岐名で十分に表せるなら `when` / `switch` を優先する
5. 概念として名前を付けることで理解が進むなら Strategy を許容する

## 早すぎるキャッシュ戦略の導入

AIはパフォーマンスを「改善」するためにキャッシュ機構を先回りで導入しがちである。明示的に要求されるまでキャッシュ戦略は追加しない。

| パターン | 例 | 判定 |
|---------|-----|------|
| データ取得にキャッシュレイヤー追加 | stale-while-revalidate設定、インメモリキャッシュ、Redis導入 | REJECT |
| ローカルへのキャッシュ保存 | APIレスポンスをローカルストレージやファイルに保存して再利用 | REJECT |
| 計測なしのメモ化 | ボトルネック未特定のままメモ化を多用 | REJECT |
| キャッシュ無効化の独自実装 | TTL、キャッシュキー管理、パージ機構の追加 | REJECT |

判断基準: 「キャッシュが必要」という明示的な要求または計測結果があるか?
- YES → 実装してよい
- NO → 実装しない。素朴なデータ取得で十分

## デッドコード検出

AIは新しいコードを追加するが、不要になったコードの削除を忘れることが多い。

| パターン | 例 |
|---------|-----|
| 未使用の関数・メソッド | リファクタリング後に残った旧実装 |
| 未使用の変数・定数 | 条件変更で不要になった定義 |
| 到達不能コード | 早期returnの後に残った処理、常に真/偽になる条件分岐 |
| 論理的に到達不能な防御コード | 呼び出し元の制約で絶対に通らない分岐 |
| 未使用のインポート・依存 | 削除された機能のimport文やパッケージ依存 |
| 孤立したエクスポート・公開API | 実体が消えたのにre-exportやindex登録が残っている |
| 未使用のインターフェース・型定義 | 実装側が変更されたのに残った古い型 |
| 無効化されたコード | コメントアウトされたまま放置されたコード |

論理的デッドコードの検出:

AIは「念のため」の防御コードを追加しがちだが、呼び出し元の制約を考慮すると到達不能な場合がある。構文的には到達可能でも、呼び出しチェーンの前提条件により論理的に到達しないコードは削除する。

```typescript
// REJECT - 呼び出し元がインタラクティブ入力を前提としている
// 非インタラクティブ環境からこの関数が呼ばれることはない
function displayResult(data: ResultData): void {
  const isInteractive = process.stdin.isTTY === true;
  // isInteractive は常に true（呼び出し元がTTYを前提としている）
  const output = isInteractive ? formatRich(data) : formatPlain(data);  // else節は到達不能
}

// OK - 呼び出し元の制約を理解し、不要な分岐を排除
function displayResult(data: ResultData): void {
  // インタラクティブメニューからのみ呼ばれるためTTYは常に存在する
  console.log(formatRich(data));
}
```

検証アプローチ:
1. 防御的な分岐を見つけたら、grep でその関数の全呼び出し元を確認
2. 全呼び出し元が既にその条件を満たしている場合、防御は不要
3. 変更・削除されたコードを参照している箇所がないか grep で確認
4. 公開モジュール（index ファイル等）のエクスポート一覧と実体が一致しているか確認
5. 新規追加されたコードに対応する古いコードが残っていないか確認

## フォールバック・デフォルト引数の濫用検出

AIは不確実性を隠すためにフォールバックやデフォルト引数を多用する。

| パターン | 例 | 判定 |
|---------|-----|------|
| 必須データへのフォールバック | `user?.id ?? 'unknown'` | REJECT |
| デフォルト引数の濫用 | `function f(x = 'default')` で全呼び出し元が省略 | REJECT |
| null合体で渡す口がない | `options?.cwd ?? process.cwd()` で上位から渡す経路なし | REJECT |
| try-catch で空値返却 | `catch { return ''; }` | REJECT |
| 多段フォールバック | `a ?? b ?? c ?? d` | REJECT |
| 条件分岐でサイレント無視 | `if (!x) return;` で本来エラーをスキップ | REJECT |

検証アプローチ:
1. 変更差分で `??`、`||`、`= defaultValue`、`catch` を grep
2. 各フォールバック・デフォルト引数について:
   - 必須データか? → REJECT
   - 全呼び出し元が省略しているか? → REJECT
   - 上位から値を渡す経路があるか? → なければ REJECT
3. 理由なしのフォールバック・デフォルト引数が1つでもあれば REJECT

## 未使用コードの検出

AIは「将来の拡張性」「対称性」「念のため」で不要なコードを生成しがちである。現時点で呼ばれていないコードは削除する。

| 判定 | 基準 |
|------|------|
| REJECT | 現在どこからも呼ばれていないpublic関数・メソッド |
| REJECT | 「対称性のため」に作られたが使われていないsetter/getter |
| REJECT | 将来の拡張のために用意されたインターフェースやオプション |
| REJECT | exportされているが、grep で使用箇所が見つからない |
| OK | フレームワークが暗黙的に呼び出す（ライフサイクルフック等） |

検証アプローチ:
1. 変更・削除されたコードを参照している箇所がないか grep で確認
2. 公開モジュール（index ファイル等）のエクスポート一覧と実体が一致しているか確認
3. 新規追加されたコードに対応する古いコードが残っていないか確認

## 不要な後方互換コードの検出

AIは「後方互換のために」不要なコードを残しがちである。これを見逃さない。

削除すべき後方互換コード:

| パターン | 例 | 判定 |
|---------|-----|------|
| deprecated + 使用箇所なし | `@deprecated` アノテーション付きで誰も使っていない | 即削除 |
| 新APIと旧API両方存在 | 新関数があるのに旧関数も残っている | 両方に使用箇所がある場合を除き、旧を削除 |
| 移行済みのラッパー | 互換のために作ったが移行完了済み | 削除 |
| コメントで「将来削除」 | `// TODO: remove after migration` が放置 | 今すぐ削除 |
| Proxy/アダプタの過剰使用 | 後方互換のためだけに複雑化 | シンプルに置換 |

残すべき後方互換コード:

| パターン | 例 | 判定 |
|---------|-----|------|
| 外部公開API | npm パッケージのエクスポート | 慎重に検討 |
| 設定ファイル互換 | 旧形式の設定を読める | メジャーバージョンまで維持 |
| データ移行中 | DBスキーマ移行の途中 | 移行完了まで維持 |

判断基準:
1. 使用箇所があるか? → grep/検索で確認。なければ削除
2. 新旧両方に使用箇所があるか? → 両方が現在使われているなら、後方互換ではなく並存設計の可能性がある。呼び出し元を確認
3. 外部に公開しているか? → 内部のみなら即削除可能
4. 移行は完了したか? → 完了なら削除

AIが「後方互換のため」と言ったら疑う。本当に必要か確認せよ。

## 決定トレーサビリティレビュー

Coderの決定ログが妥当か検証する。

| 確認項目 | 質問 |
|---------|------|
| 決定が文書化されている | 自明でない選択は説明されているか? |
| 理由が妥当 | 理由は理にかなっているか? |
| 代替案が検討されている | 他のアプローチは評価されたか? |
| 仮定が明示されている | 仮定は明示的で合理的か? |
