はじめに
こんにちは。食べログカンパニー 開発本部 飲食店プロダクト開発部 運用改善チームの @4palace です。
食べログには2019年から参画しています。
長く運用されるシステムには、いつの間にか誰も呼んでいないコード──デッドコードが溜まっていきます。
「消せばいいだけでしょ?」と思われがちですが、大規模システムでは話が違います。
いくら慎重に調査しても、一歩間違えれば本番障害に直結する──デッドコード削除はそういう怖さがある作業です。
私たちのチームでは、この「消す怖さ」と向き合い続けてきました。
以前は人力による調査を中心にデッドコード削除を進めていましたが、着実に消せる一方でそれなりの工数がかかり、調査品質も担当者のスキルに左右されがちでした。
そこで現在は、AIコーディングエージェント(Claude Code)と2段階リリースを組み合わせた運用設計を構築し、継続的にデッドコードを削除しています。
本記事では、その仕組みと、AIを「時短ツール」ではなく「運用部品」として組み込むために考えたことをお伝えします。
その過程で、AIをうまく活用するとはツールを使いこなすことではなく、AIが機能するように運用を設計することだと気づきました。
事故から学んだこと:「調査が正しい」≠「正しく消せる」
まず、現在の運用設計に至るきっかけとなった事故の話をします。
あるとき、人力で調査を行い、コードレビューも経て、デッドコード削除をリリースしました。
調査結果どおりのメソッドが削除対象になっていることを確認し、大丈夫だろうと思いながらリリースを見守りました。
ところが、リリース後にシステムエラーが発生。慌てて切り戻しました。
原因を調べてみると、クラス名もメソッド名も同じでした。
しかし実際には、異なる名前空間に属する同名クラスの同名メソッドを取り違えて削除していたのです。
つまり、調査自体は正しかったのに、消したものは別物でした。
この教訓から、2つの課題が明確になりました。
- 取り違えを防ぐ仕組みがない:調査が正しくても、実装段階で別物を消してしまうリスクがある
- ミスが即座に本番障害になる:確認なしに一発で削除するフローでは、ミスの影響を止められない
これらを解決するために、2段階削除方式とAI調査を組み合わせた運用を設計しました。
全体像:運用フロー
まず起点となるのは、社内の未実行メソッド解析ツールによる削除候補の抽出です。
このツールは、本番環境で一定期間実行されなかったメソッドを一覧化するもので、ここで得られた一覧が処理対象になります。
仕組みの詳細は以下の発表資料にまとめています。
食べログのデッドコード解析基盤の裏側 - TECH Street Ruby勉強会
この候補一覧をもとに、以下の流れでデッドコードを削除します。

ポイントは以下の3点です。
- 2段階(Phase1 → 経過観察 → Phase2)でリリースしていること(事故を防ぐガードレール設計)
- 調査から実装・PR作成までをClaude Codeでほぼフルオート化していること(調査ルールの標準化による自動化)
- 人間はコードレビューとリリース判断に集中していること(AI/人間の役割分担の明確化)
各ステップにおける自動化と人間の関与の境界は以下のとおりです。
| ステップ | 自動(ツール/AI) | 人間 |
|---|---|---|
| 1. 削除候補の抽出 | 解析ツールで一覧化 | - |
| 2. Phase1(調査・実装) | Claude Codeで調査〜ログ埋め込みPR作成 | - |
| 3. Phase1(レビュー) | - | 調査妥当性の確認・マージ |
| 4. 経過観察 | - | ログの通過確認(一定期間) |
| 5. Phase2(削除実装) | Claude Codeで対象コードの削除PR作成 | - |
| 6. Phase2(最終確認) | - | 削除PRの確認・リリース判断 |
この運用設計によって、安全性を担保したデッドコード削除フローが回せるようになりました。
それぞれ詳しく説明していきます。
調査ルールの標準化:AIを「運用部品」にする
まず、調査そのものの工数や品質のばらつきの課題に対するアプローチです。
ここに登場するのがClaude Codeです。
デッドコード調査の本質は「呼び出し元の調査」です。しかし、動的な呼び出し・フレームワーク経由の暗黙的な呼び出し・リフレクションなど、単純なgrepでは見つけられないケースが多く、調査担当者のスキルに依存しがちでした。
そこで、呼び出し元を網羅的に調査させるためのルールを整備し、Claude Codeに読み込ませています。このルールはClaude Codeと壁打ちしながら整備しており、現在も継続的に改善しています。
例えば以下のように検索パターンをルール化し、調査をさせます。
## 調査フロー(パターン1-A〜1-E の実施手順)
調査対象: `Foo::Bar::ClassName` の場合
### Step 1: パターン1-A(直接参照)
Grep(pattern: "Foo::Bar::ClassName", glob: "**/*.{rb,slim,erb,haml,rake}")
### Step 2: パターン1-B(文字列リテラル)
Grep(pattern: '"Foo::Bar::ClassName"', glob: "**/*.{rb,slim,erb,haml,rake,yml,yaml}")
Grep(pattern: "'Foo::Bar::ClassName'", glob: "**/*.{rb,slim,erb,haml,rake,yml,yaml}")
### Step 3: パターン1-C(動的ロード)
Grep(pattern: "constantize", glob: "**/*.rb")
Grep(pattern: "const_get", glob: "**/*.rb")
# 該当箇所で "ClassName" が生成されているか確認
### Step 4: パターン1-D(ジョブキュー)
**Workerクラスの場合のみ実施**
# Resque
Grep(pattern: "Resque\.enqueue", glob: "**/*.rb")
Grep(pattern: "Resque\.enqueue_to", glob: "**/*.rb")
Grep(pattern: "Resque::Enqueuer", glob: "**/*.rb")
# Sidekiq
Grep(pattern: "perform_async", glob: "**/*.rb")
Grep(pattern: "perform_at", glob: "**/*.rb")
# ActiveJob
Grep(pattern: "perform_later", glob: "**/*.rb")
# Workerクラス名での検索
Grep(pattern: "ClassName", glob: "**/*.rb")
### Step 5: パターン1-E(動的メソッド呼び出し)
**メソッド調査の場合に実施**
# シンボル形式
Grep(pattern: ":method_name", glob: "**/*.{rb,slim,erb,haml,rake}")
# 文字列形式
Grep(pattern: '"method_name"', glob: "**/*.{rb,slim,erb,haml,rake}")
Grep(pattern: "'method_name'", glob: "**/*.{rb,slim,erb,haml,rake}")
# send/public_send の使用箇所確認(必要に応じて)
Grep(pattern: "\.send", glob: "**/*.rb")
Grep(pattern: "\.public_send", glob: "**/*.rb")
調査結果もルールに基づいた形式で出力させ、「どの観点で調査し、結果はどうだったか」をパターンごとに記載させます。
## 管理番号: DEAD_CODE_20XX_XXXX ### 調査対象 - ファイルパス: `tabelog_lib/foo/bar/baz.rb` - クラス名: `Foo::Bar::Baz` - メソッド名: `some_method` ### 調査結果 - パターン1-A(直接参照): 使用箇所なし - パターン1-B(文字列リテラル): 使用箇所なし - パターン1-C(動的ロード): 使用箇所なし - パターン1-D(ジョブキュー): 使用箇所なし - パターン2(同一ファイル内呼び出し): 使用箇所なし - パターン3(委譲パターン): 使用箇所なし - フレームワークパターン(コールバック等): 該当なし ### 結論 全パターンで使用箇所が見つからなかったため、**デッドコードと判定**します。
さらに、メソッド一覧から調査対象を指定する手間も省きました。
一覧を管理しているスプレッドシートからそのままコピペするだけで調査を開始できます。
プロンプトは以下でOKです。
以下のメソッドがデッドコードかどうか調査してください。 1 lib tabelog_lib/some_class.rb #foo_dead_method 2 lib tabelog_lib/some_class.rb #bar_dead_method 3 lib tabelog_lib/some_class.rb #baz_dead_method
ガードレール:2段階削除方式
調査ルールの標準化により調査品質は安定しましたが、それでも「消してはいけないものを消さない」ための仕組みは別途必要です。
起点となる未実行メソッド解析ツールは、計測の仕組み上、すべての呼び出し経路を網羅できるわけではありません。
例えばバッチスクリプトから直接実行されるメソッドや、ビューから直接呼ばれるケースでは、実際には使われていても「未通過(実行記録がない)」と判定されることがありました。
つまり、解析ツールの結果だけでは「本当に未通過か」を確証できないケースがあるのです。
そこで、対象メソッドが実際に実行されたかどうかを呼び出し経路に依存せず記録する、通過監視ログの仕組み(DeadCodeTracker)を導入しました。
以下の要件を満たすよう設計しています。
- 通過した場合はきちんと完全修飾クラス名を確認できること(取り違えを防ぐため)
- どこから呼ばれても記録されること(これまでの解析ツールで拾えないところをカバーするため)
- 実装時にできるだけシンプルにログの埋め込みができること(実装時に考える余地を減らすため)
Phase1では、呼び出し元を調査してデッドコードと思われるメソッドに対し、このログ記録処理を埋め込みます。
class SomeClass def dead_method + DeadCodeTracker.track_execution(self, __method__) # なにがしかの未実行コード end end
記録処理の追加をできるだけ機械的に行うため、記録メソッドのインターフェースは必要最小限としています。
この記録処理の埋め込みはClaude Codeが自動で行います。
コミット粒度もレビューしやすいよう、1ファイル = 1コミットを基本としています。
また、DeadCodeTracker の追加に合わせて、ログが適切に記録されることを確認するテストケースも生成させています。
これにより、ログ埋め込み自体が機能していなかった、というミスを防ぎやすくなりました。
(テスト作成のコストも小さくないため、この点でも工数削減に寄与しています)
調査結果はファイルに出力されるため、人間はコードレビューの際に調査の妥当性も合わせて確認したうえで、リリース判断を行います。これがPhase1です。
Phase1のあと、一定の経過観察期間を設けます。この期間は、定常バッチや周期的な処理が一巡することを目安に設定しています。
実運用では、まず1週間程度の経過観察を行い、その間にログの記録がないことを確認できたものからPhase2に進めていました。
経過観察期間を経たうえでログの記録がないこと(未通過であること)を確認できれば、削除PRを作成します。
class SomeClass - def dead_method - DeadCodeTracker.track_execution(self, __method__) - # なにがしかの未実行コード - end end
これによって、削除時にコードレビュアーは以下の状態を確認できます。
- 消すべきメソッドの取り違えが発生していないこと
- 対象メソッドは通過ログ記録期間を経たものであること
- 通過ログ記録を経ていないものを消していないこと
このガードレールのおかげで、調査に見落としが紛れ込んでいたとしても、本番障害に直結するリスクを大幅に軽減できます。安全に削除を進められるようになりました。
Phase2の削除もClaude Codeが自動で行います。プロンプトは調査時とほとんど変わりません。
以下の対象に対して、Phase2の削除をお願いします。 1 lib tabelog_lib/some_class.rb #foo_dead_method 2 lib tabelog_lib/some_class.rb #bar_dead_method 3 lib tabelog_lib/some_class.rb #baz_dead_method
この仕組みはAIのためだけのものではありません。人間が調査しても取り違えは起こり得ます。
AIを組み込むなら尚のこと、「誰がやっても安全」な構造を先に用意することが重要でした。
ここまでが、AIを組み込んだデッドコード削除運用フローの実態です。
調査から実装・PR作成まではClaude Codeがほぼフルオートで行い、人間はコードレビューとリリース判断に集中する形になっています。
今後はコードレビューのAI支援もさらに進め、人間の関与をリリース判断に絞っていくことを目指しています。
成果
AI導入による成果を、導入前後のそれぞれ4ヶ月間の集計データで比較しました。 (※数値は2026年3月時点のものです)
| 観点 | Before | After |
|---|---|---|
| 削除メソッド・行数 | 1,134メソッド、9,775行 | 2,410メソッド、14,303行 |
| 体制・工数 | 5名で集中実施(他作業を中断) | 3名で他運用と並行、毎週リリース継続 |
| 削除対象領域 | 局所的領域(特定サービス機能に閉じている) | 共有領域(より慎重な作業が必要) |
| 安全性 | 削除後リバート事故あり | 事故なし(継続中) |
※有効行ベースの集計。純テストコードなどの関連ファイルを含めたPR全体では23,600行を超える規模。
※Before/Afterは削除対象の性質や進め方が完全に同一条件ではないため、単純比較ではなく傾向としてご参照ください。
数字だけでなく、運用面でも大きな変化がありました。
- 調査工数の削減:AI実行中は張り付く必要がなく、他の運用作業と並行できる
- 分業・並列化:ルールを共有することで、複数人への分担・並列調査が可能に
- 品質の平準化:エンジニアの習熟度に依存せず、安定した調査クオリティを担保
- レビュー負荷の軽減:調査観点と結果が定型化されており、レビュアーが確認しやすい
より高リスクな共有領域を対象にしながらも、少人数で事故なく毎週リリースを継続できています。
こうした成果を日常的な運用のなかで出せた背景には、弊社が掲げるAI EXCELLENCEというミッションがあります。
AI EXCELLENCE(AIで卓越した成果を出す) とは、「全社員が日常業務でAIを自然に使いこなし成果を出していく」という考え方です。
今回の取り組みは、特別なプロジェクトとしてではなく運用業務の一環としてAIを活用し成果を出した点で、まさにこのミッションの体現に向けての一歩を踏み出せたと感じています。
まとめ:AIの効果は、自動化より運用設計で決まる
今回の取り組みで最も重要だった考え方は、「AIは間違える」を前提に運用を設計するということです。
AIの調査精度がどれだけ向上しても、100%の正確性は保証されません。人間の確認ミスもゼロにはなりません。
だからこそ、間違えても事故にならない仕組み(2段階削除)と、間違えにくくする仕組み(調査ルールの標準化)を両輪で回す設計にしました。
この前提があったからこそ、AIに「信頼して任せる」ことができました。
この考え方を具体的に落とし込んだのが、以下の3点です。
成果物をレビューしやすい単位に揃えたこと
AIは速く・大量に成果物を作れるからこそ、マージ判断をする人間のレビューがボトルネックになりがちです。
「何が入っていれば良いか・何を調査していれば良いか」を明確にすることで、AIの処理速度を活かしたままリリースできる仕組みを整えました。ミスが発生しても被害を最小化する運用にしたこと
「ログ確認が完了したメソッドのみを削除する」仕組みで取り違えを防ぎ、
「ミスがあっても直ちに本番に影響しない」2段階構成で、調査精度への過度な依存を避けました。無理なく継続できる作業設計にしたこと
プロンプトやルールが人依存になっていたり逐一確認が必要な状態では、AIを導入した意味がありません。
「依頼すれば網羅的にやってくれて、成果物チェックもしやすい」ルールの標準化を徹底しました。
専用プロジェクトを組まずに、運用業務の一環として継続できる形です。
この考え方は、デッドコード削除に限りません。リファクタリングやセキュリティリスクの調査など、技術負債の解消を継続的に回したいあらゆるタスクに適用できるはずです。
ポイントは「AI出力をどう運用に載せるか」──AIを使いこなすのではなく、AIが機能する運用を設計することだと考えています。
最後まで読んでいただき、ありがとうございました。
最後に、食べログでは一緒に働く仲間を募集しています!
食べログに興味を持ってくださった方は、以下の採用情報リンクからぜひご応募ください!