
AIにデッドコード・重複を洗わせる|安全に消すための確認手順
リポジトリを開くと、いつからあるのか分からない関数がいくつも残っている。名前からして、たぶんもう使っていない。でも、消してビルドが通っても、本番のどこかでこっそり呼ばれていたらと思うと、指が止まる。
そこでAIに「未使用のコードを洗い出して」と頼むと、一覧はあっけないほど早く出てきます。ずらりと並んだ関数名を見て、少しだけほっとして——次の瞬間、この一覧、どこまで信じていいんだろうと、また手が止まる。
その慎重さは、正しい感覚です。デッドコードの判定は、AIがいちばん自信満々に外しやすい種類の仕事だからです。この記事では、AIに探させるところと、人が裏を取るところをはっきり分けて、戻せる形で少しずつ消していく手順を整理します。全部を一度にきれいにする話ではありません。今日、1つだけ安心して消せるようになるための型です。
結論:AIは「消す候補を挙げる」のは得意ですが、「消してよいと判定する」のは苦手です。だから、①範囲を1ディレクトリ・1テーマに絞って洗わせる → ②AIの一覧を"候補"として受け取る(判断はさせない) → ③静的解析・全文検索・実行ログの3方向で裏を取る → ④1テーマずつ、戻せる単位で消す、の順で進めます。消す判断は人が持ったまま、探す手間だけAIに預ける。それが安全で、いちばん速い進み方です。
削除は、機能追加と違って「うまくいっても誰も気づかない」仕事です。だからこそ、うまくいかなかったときにすぐ戻せる形にしておくことが、そのまま安心につながります。
なぜ「消せないコード」がたまっていくのか
責任は、たぶん誰にもありません。コードが消えずに残るのには、はっきりした理由があります。
- 消す作業に、直接のメリットが見えにくい:機能は増えないのに、壊すリスクだけがある。後回しになるのは自然なことです。
- 経緯を知っている人がいない:「たぶん昔のキャンペーン用」までは分かっても、確証が持てない。
- 一時的に止めただけのつもりだった:フラグでオフにして、そのまま数年経っている。
- 消し忘れが積み重なる:置き換えた新しい実装だけを入れて、古いほうを消すのを次回に回した。
そしてもうひとつ、最近特有の理由があります。AIに書かせるコードが増えると、似た処理が増えやすいことです。前に頼んだことを覚えていないAIは、必要になるたびに新しい関数を作ります。一つひとつは正しくても、リポジトリ全体で見ると、同じような処理が少しずつ違う形で散らばっていく。
つまりこれは、あなたの片付けが下手だから起きているのではなく、開発の進め方の副産物です。だからこそ、意志の力ではなく手順で片付けます。
AIが得意なこと・苦手なこと

まず、AIとの役割分担をはっきりさせておきます。ここがぼやけたまま進むと、結局いちばん危ない使い方になります。
AIが得意なこと(任せてよい)
- 広い範囲をざっと眺めて、候補を漏れなく挙げる
- 「これとこれ、処理がほぼ同じでは?」と似ている箇所を並べる
- 消したあとの影響範囲を説明させる(説明を読むのは人)
- 消す順番のたたき台を作る
AIが苦手なこと(人が持つ)
- 本当に呼ばれていないかの判定:AIは渡された範囲しか見ていません。別リポジトリや設定ファイルからの参照は、そもそも視界に入っていません。
- 「たまたま似ている」と「同じ役割」の区別:見た目が似ているだけの処理を、意味まで同じだと判断しがちです。
- 消してよい/よくないの最終判断:根拠が薄くても、はっきりした口調で「不要です」と返ってきます。
いちばん危ないのは、AIが自信を持って断言してくれることです。「この関数はどこからも参照されていないため、安全に削除できます」——この一文は、AIのハルシネーション(もっともらしい誤り)と同じ顔つきで出てきます。文章の強さは、根拠の強さではありません。
だから、AIには候補を出させるところまでにして、判定は次章の3方向の裏取りに任せます。
消してはいけない「使われていないように見えるコード」
裏取りの話に入る前に、静的な参照検索では見つけにくい呼ばれ方を押さえておきます。ここを知らないと、grepで0件だったから安心、と進んでしまいます。
- 文字列でメソッド名を指定して呼ぶ:リフレクションや動的ディスパッチ。コード上に関数名の直接参照が現れません。
- フレームワークの規約で自動的に呼ばれる:命名規則やアノテーションで拾われるハンドラ、ライフサイクルメソッド、イベント購読など。
- 設定ファイル・DIコンテナから指定されている:YAMLやJSONにクラス名・関数名が文字列で書かれているパターン。
- テンプレートやフロント側から参照されている:HTMLテンプレート、SQL、シェルスクリプト、ジョブ定義など、検索対象の言語の外にある参照。
- 別リポジトリ・別サービスから使われている:公開しているAPIやライブラリ、バッチ、社内の別チームのツール。
- 機能フラグでいま止まっているだけ:オフになっているだけで、消してよいとは限りません(機能フラグと段階リリース)。
- 障害時・例外時にだけ通る:普段は通らないフォールバック経路。ログにもめったに出ません。
- テストからだけ呼ばれている:本体が未使用なのか、テストが仕様を守っているのかは、中身を見ないと分かりません。
「未使用に見えるけれど使用中」の代表格が、この8つです。AIに候補を出させるときは、この観点で自己申告させると、見落としがぐっと減ります(プロンプトの型は後半に載せます)。
安全に消す4つのステップ
流れはこうです。この順に進めると、途中で怖くなっても、いつでも止まれます。
| ステップ | やること | ひとことで言うと |
|---|---|---|
| ①絞る | 洗う範囲を1テーマに限定する | 「全部やらない」 |
| ②洗う | AIに候補と根拠を出させる | 「判定はさせない」 |
| ③裏を取る | 解析・検索・ログの3方向で確認 | 「本当に未使用か」 |
| ④消す | 戻せる単位で少しずつ消す | 「いつでも戻せる」 |
いちばん効くのは、地味ですが①の「絞る」です。ここを飛ばして「リポジトリ全体の未使用を洗って」と頼むと、精度の低い長大な一覧が返ってきて、結局どれも消せません。
ステップ1:洗う範囲を1テーマに絞る
範囲を狭くするほど、AIの精度は上がります。AIコーディングの安全なスコープ設計と同じ考え方です。
絞り方の例です。
- 1ディレクトリだけ:「このモジュール配下だけ見て」
- 1つの終わった機能だけ:「去年終了したキャンペーン関連の処理だけ」
- 1種類の重複だけ:「日付フォーマット処理の重複だけ」
- 置き換え済みの旧実装だけ:「新しいAPIクライアントに移行した後の、旧クライアント周り」
このとき、AIにはコードだけでなく前提も一緒に渡します。「このサービスは他チームからも呼ばれている」「バッチは別リポジトリにある」——こうした既存コードベースの文脈を先に伝えるだけで、無責任な「削除可能です」が減ります。
ステップ2:AIに候補と根拠を出させる(判定はさせない)
ここでの頼み方のコツは、結論ではなく材料を出させることです。「消してよいか」ではなく、「消してよいか判断するために、私は何を確かめればいいか」を聞きます。
出させたいのは、この4つです。
- 候補の場所(ファイルと関数・クラス名)
- なぜ未使用と思ったかの根拠(どこを見て、何が無かったのか)
- 確信度と、その理由(「渡された範囲では参照が見つからない」なのか「明らかに旧実装」なのか)
- 見えていない参照経路の可能性(前章の8パターンのうち、どれに当てはまりうるか)
4つ目が肝心です。AIに自分の視界の外を自己申告させると、「設定ファイルからの参照までは確認できていません」といった但し書きが付いてきます。この但し書きが、そのまま次のステップの確認リストになります。
重複コードを洗わせるときは、もう一つ質問を足します。「これは同じ役割ですか、それとも、たまたま似ているだけですか」。似ているからという理由だけで無理に共通化すると、あとで片方だけ仕様が変わったときに、共通化した関数がフラグだらけになっていきます。重複の解消は、目的が同じものだけ。ここは人が決める場所です。
ステップ3:3方向で裏を取る
AIの候補が出たら、AIの外で確かめます。3方向から見ると、見落としがかなり減ります。
(1) 言語の仕組みで確かめる
まずは、その言語やビルドツールが元から持っている未使用検出に頼ります。コンパイラ・型チェッカの未使用警告、Linterの未使用変数・未使用インポート検出、未使用エクスポートの検出ツールなど、言語ごとに定番の仕組みがあります。AIの推測と違って、これらは構文を解析した結果なので、根拠がはっきりしています。
ただし、これらも前章の動的な呼び出しは苦手です。「機械が0件と言った」も、まだ根拠の1つ目と考えてください。
(2) 全文検索で、言語の外まで探す
次に、関数名・クラス名をリポジトリ全体の全文検索にかけます。ポイントは、対象を広げることです。
- 設定ファイル(YAML・JSON・env・XML など)
- テンプレート、SQL、シェルスクリプト、ジョブ定義、CI設定
- ドキュメント(残っているなら、経緯の手がかりになります)
- 別リポジトリ(社内で参照されていないか。ここが漏れやすいところです)
名前がありふれている場合は、部分一致で拾いすぎることもあります。逆に、文字列で組み立てられた名前(接頭辞+変数)は、そもそも引っかかりません。検索で見つかったら使用中、見つからなくても未使用とは限らない——この非対称を覚えておくと安全です。
(3) 実際に通ったかをログで確かめる
いちばん確実なのは、本番で通っているかどうかです。
- 既存のログ・監視・トレースに、その処理を通った記録があるか
- なければ、削除の前に「通ったら1行ログを出す」だけを入れて、しばらく様子を見る
この「消す前に、まず見えるようにする」は遠回りに見えて、いちばん心臓に優しいやり方です。数週間から、月次・年次の処理が絡むならその周期をまたぐまで待ちます。年に1回しか動かない処理を、2週間の無風で判断してはいけません。何をログに出しておけばよかったかの観点は、ログ出力の不足をAIに指摘させるにまとめました。
3方向のうち、(3)が取れるなら(3)がいちばん強い根拠です。急ぎでないなら、ログを仕込んで待つ。それが結局、いちばん速いことが多いです。
ステップ4:戻せる単位で消す
裏が取れたら、いよいよ消します。ここでも一気にはいきません。
- 1コミット1テーマ:「旧APIクライアントの削除」だけ。整形やリネームを混ぜない。混ざると、あとで戻すときに戻したくないものまで戻ることになります。
- 削除だけのコミットにする:機能変更と同じコミットに入れない。複数ファイルにまたがる変更の差分の追い方と同じで、差分が単一目的だと読む側もレビューが楽です。
- 消したら、既存テストを通す:ここで落ちるなら、それは「使われていた」という何よりの証拠です。リグレッションを防ぐ既存テストの文脈も合わせてどうぞ。
- テストが落ちないことは、安全の証明ではない:テストが薄い領域なら、テストは何も言ってくれません。カバレッジと"意味のあるテスト"の差のとおりです。
- 戻し方を先に決めておく:どのコミットを戻せば元通りか、消す前に確認しておきます。「消したコードはリポジトリの履歴に残っている」——これが、削除を怖くなくしてくれる事実です。
- リリース後、しばらくは注意して見る:エラーの増減を1〜2週間だけ気にかける。それで十分です。
コメントアウトして残す、という選択肢もありますが、あまりおすすめしません。消えていないコードは、次の人にとって"消していいのか分からないコード"のままです。履歴に残っているなら、思い切って消したほうが、リポジトリは軽くなります。
ありがちな落とし穴と、その回避
- AIの「安全に削除できます」を根拠にする:それは断言であって、確認ではありません。根拠を聞き返して、3方向で裏を取る。
- grepが0件だから消す:文字列で呼ばれる経路は引っかかりません。動的呼び出しの可能性を必ず一度は考える。
- 削除と整形を同じコミットに混ぜる:戻したいときに戻せなくなります。削除は削除だけ。
- 似ているから共通化する:目的が違う処理を1つにすると、あとで分岐だらけになります。共通化は「同じ役割」だけ。
- 一気にきれいにしようとする:大きな削除PRはレビューされないまま放置されがちです。小さく、何回かに分けて。
- 年に一度の処理を短期間で判断する:月次・年次バッチは、周期をまたぐまで待つ。
どれも、根っこは同じです。「消してよい」は証明が要る側の主張で、「まだ消さない」は保留のままでいい。急がなくて大丈夫です。
明日からやること(小さく始める3つ)
いきなりリポジトリ全体に手を付けず、まずこの3つから。
- 1ディレクトリだけ選んで、AIに候補と根拠を出させる:判定は求めず、「何を確かめればいいか」まで聞く。
- 候補を1つだけ選んで、全文検索を設定ファイルとテンプレートまで広げてかける:これだけで「実は使用中」が見つかることがあります。
- 確信が持てない1つに、ログを1行だけ仕込む:判断を、来月の自分に渡してあげる。これも立派な前進です。
今日ひとつ消せなくても大丈夫です。「これは未使用、これは要確認」と仕分けできた時点で、リポジトリはもう整理され始めています。
コピーして使うプロンプトの型
未使用コードの候補を洗わせる
以下のコードについて、使われていない可能性のある関数・クラス・ファイルの
「候補」を挙げてください。削除してよいかの判断はしないでください。
【前提】
- 対象範囲:(ディレクトリ/機能を書く)
- このコードは(他チーム/別リポジトリ/バッチ)からも呼ばれる可能性が(ある/ない)
- フレームワーク・DIコンテナ:(あれば書く)
【出してほしいもの】各候補について、
1. 場所(ファイル・シンボル名)
2. 未使用と考えた根拠(どこを見て、何が無かったか)
3. 確信度(高/中/低)とその理由
4. あなたからは見えていない参照経路の可能性
(リフレクション等の動的呼び出し/フレームワークの規約による自動呼び出し/
設定ファイル・DIからの指定/テンプレート・SQL・ジョブ定義/別リポジトリ/
機能フラグでオフ中/例外時のみ通る経路/テストからのみ参照)
渡した範囲の外は「確認できていない」と正直に書いてください。
重複コードを洗わせる
以下のコードから、処理が重複している箇所の候補を挙げてください。
各候補について、次を分けて書いてください。
- どこが同じで、どこが違うか(差分を具体的に)
- 「同じ役割の重複」か「たまたま似ているだけ」か、その判断理由
- 共通化した場合に増える引数・分岐(複雑になるなら、そう書いてください)
- 共通化しないほうがよい理由が思いつく場合は、それも
共通化の実装はまだしないでください。まず候補と判断材料だけをください。
消す前のチェックリスト(コピーして使えます)
削除の候補ごとに、上から確認します。全部に○が要るわけではなく、(3)のどれか1つでも根拠が取れているかが目安です。
候補を受け取ったとき
- AIの「削除可能」を、断言ではなく候補として扱ったか
- 未使用と考えた根拠を、AIに言葉で説明させたか
- AIから見えていない参照経路の可能性を、自己申告させたか
- 対象範囲を1ディレクトリ・1テーマに絞ったか
消す前の裏取り
- コンパイラ・型チェッカ・Linterの未使用検出を通したか
- 関数名・クラス名を、設定ファイルとテンプレートまで含めて全文検索したか
- 別リポジトリ・別サービスからの参照を確認したか
- 動的な呼び出し(文字列指定・フレームワークの規約)の可能性を考えたか
- 機能フラグでオフになっているだけではないか確認したか
- 月次・年次など、長い周期の処理ではないか確認したか
- 確信が持てないものに、ログを仕込んで様子を見る選択をしたか
消すとき
- 削除だけのコミットにしたか(整形・リネームを混ぜていないか)
- 1コミット1テーマになっているか
- 既存のテストが通ることを確認したか
- テストが薄い領域でないか、一度考えたか
- 戻すときにどのコミットを戻せばよいか、分かる状態か
- リリース後しばらく、エラーの増減を見る段取りがあるか
重複を共通化するとき
- 「同じ役割」か「たまたま似ているだけ」かを、自分の言葉で説明できるか
- 共通化で引数やフラグが増えすぎていないか
- 片方だけ仕様が変わったときに、無理なく分けられる形か
最後に
削除は、報われにくい仕事です。うまくやっても機能は増えず、誰も気づかない。それでも誰かがやらないと、次に触る人が同じところで指を止め続けることになります。

消せなかったコードが残っているのは、あなたが慎重すぎたからではありません。壊さないことを優先してきた、それだけのことです。その慎重さは、これからも手放さなくていい。慎重なまま、確かめる手順だけを増やせば、少しずつ消していけます。
一度に全部きれいにしなくて大丈夫です。今日ひとつ「これは未使用」と言い切れたなら、それはもう、次に触る人への贈り物になっています。
よければ、こちらも
- 差分の読み方を整えたいときはAIリファクタリングの差分レビュー|挙動を変えていないかの確認
- 直す順番に迷うときはAIにコードの複雑さ・設計の臭いを指摘させる|直す順番の決め方
- 経緯の分からないコードを読むときはレガシーコードの仕様をAIに読み解かせる|仕様復元の進め方
- 生成コード全般の確認はAI生成コードのレビュー・検証チェックリスト