hacomono TECH BLOG

フィットネスクラブやスクールなどの顧客管理・予約・決済を行う、業界特化型SaaS「hacomono」を提供する会社のテックブログです!

潜在的なデッドコードを見つけて削除した話

この記事は hacomono Advent Calendar 2025 の8日目の記事です

こんにちは。今年6月に hacomono へジョインしたプロダクトエンジニアの 江口 です。社内ではニックネーム文化があり、ぐっちと呼ばれています。
hacomono ではバックエンドに Ruby on Rails、フロントエンドに Nuxt.js を利用しています。これまで私は hacomono ジョイン直前までは、フロントエンドの開発が主で、 Rails 開発経験は 10 ヶ月程度。バックエンドにはまだ苦手意識が残る入社 2 ヶ月後のある日、Rails 側の開発タスクを進めていたところ、どうしてもやりたい仕事を見つけて進めたミニプロジェクトの話をします。
それが、デッドコード削除です。

きっかけは RuboCop の強化

チーム開発で Ruby を使っているなら必ず導入しているであろう RuboCop ですが、hacomono では私の入社時点でかなりゆるいルールで運用されていました。開発者が増えている今のフェーズでは、コーディングルールが統一されない弊害や低品質なコードの混入が目立ち始めており、入社後ほどなくして RuboCop のルールが強化されることになりました。
しかし、新ルールに照らすと過去のコードで大量のルール違反が発生してしまいました。そこで、新規追加されるコードに限ってチェックを行う措置が取られました。
具体的には、CI で pronto を利用し、デフォルトブランチとの差分に対してのみ RuboCop のチェックを行っています。
このセットアップは htz さんを筆頭に、リアーキチームの iwazer さんや jun さんが実施してくれました。
これで新規機能の品質は担保される状態になりましたが、一方で既存コードを修正する際、自分が書いていないコードに対しても RuboCop から指摘されるケースが生じます。
私は過去の職場で ESLint ルールセットやカスタムルールを自作するほど、チーム開発における Linting / Formatting に強い想いを持っています。
なので、以前の自分なら「よろしい、リファクタだ…」と手を動かしていたはずです。しかし hacomono は様々なドメインのお客様に利用いただいているサービスであり、ビジネスロジックが非常に複雑なケースもあります。そのため、気軽にリファクタできないファイルがあるのも事実でした。
暫定措置として、やむを得ない事情で修正できない場合は .rubocop_todo.yml にファイルパスを追加することが許容されています。

ユニットテストがない…?

入社したばかりの私ができることは、軽微なバグチケットを修正しながら hacomono の開発フローに慣れることでした。
先輩方が選んでくれた簡単そうなチケットをアサインしていただき、開発を開始します。すると予想通り、RuboCop から既存コードに対して指摘が発生しました。当然、自分のマインドは「よろしい、リファクタだ…」です。
しかし、振る舞いを変えずに安全にリファクタを行うには、ユニットテストが必要です。hacomono のような高い品質を目指すプロダクトであれば、ユニットテストも充実しているべきです。ですが、実態としては、ユニットテストがない public メソッドやクラスが残っており、リクエストテストで一定のカバレッジは担保されているものの、エッジケースの分岐はカバーしきれていない状態です。
上記エントリが書かれた 2024 年 2 月と比較すると、状況はかなり改善してきています。とはいえ、まだ満足のいく水準には達していない(まだまだやれるぞい)というのが個人的な感想です。
このようなクラスに対して浅いドメイン理解のまま修正・リファクタを行うと、思わぬユースケースで不具合が生じる事故がしばしば発生してしまっています。入社直後からそのような現実を目の当たりにしていた私は、リファクタリングを躊躇するようになってしまいました。これに気づいた時は正直めちゃくちゃ悔しかったです。

RSpec を書けば解決するのか?

そうなれば、やることは明確です。カバーされていない分岐に対してテストを書くだけです。
しかし、クラスの振る舞いを理解することは、ドメインを理解することに他なりません。テストを書くこともまた、ドメイン理解があってこそできる作業です。
浅い理解でテストを書いても、カバレッジは上がるかもしれませんが、その仕様が正しいかは不明瞭なままで、カバレッジを上げるためだけのテストを量産するだけです。
最も問題なのは、誤ったドメイン理解により「本来 fail すべきテストシナリオが、今の実装では通るから OK」といった形で「不具合を仕様化」してしまうことです。
AI を使っても、この課題は解決できません。設計資料などのコンテキストを適切に与えない限り、AI は現在の実装を正しいものとしか解釈できないからです。
最悪のケースでは、AI が作った得体の知れないテストから、別の AI インスタンスが誤ったコンテキストを読み取り、無駄にトークンを消化し、生成結果のパフォーマンスを低下させる——そんな負の循環に陥る危険性もあります。
そこで私は、ただチケットを消化するためにテストを書いてリファクタリングするのではなく、ドメイン理解を深めるために、さらにコードを読み進めることにしました。

デッドコードの香りが漂い始める

RuboCop から指摘されていたのは、あるマスタの before_destroy hook 相当のロジックでした(以下、Rails 経験者がイメージしやすいよう before_destroy として記述しています)。まず考えたのは「本当に今もこの before_destroy が必要なのか?」という本質的な問いです。
このロジックの内容は、マスタレコード削除時に関連エンティティの項目からマスタの値が参照されていないかをアプリケーションレイヤーでチェックし、意図しないデータ不整合を防ぐというものでした。よくあるパターンです。

ところが、関連エンティティを確認すると、参照しているはずの項目はどの処理からも更新されないデータでした。つまり、before_destroy 自体を完全に削除しても問題なさそうだとわかりました。

さらに調べる*1と、before_destroy が呼ばれる可能性があるレコードの削除処理は現在4箇所のみで、具体的には、マスタレコード削除を行うユースケースクラス、そのユースケースを呼び出している管理サイト向け API の controller、ひとつの Rake task、そしてそのバッチの RSpec で不整合状態を強制的に発生させている before hook です。
Rake task を詳しく調べると、フォルダ構成から1年以上前のマイグレーション時に一度のみ実行するために作成された one-shot バッチだとわかりました。つまり、Rake task もそのテストも、今となっては削除できそうです。

本当にデッドコードなのかを確認する

まず Rake task については、内容から one-shot 用途であると推論できたため、削除する方針としました。
次に、管理サイト向け API Controller を調査しました。当該エンドポイントの URL を管理サイトの Nuxt プロジェクトから検索し、現在も呼び出される可能性を確認します。ロジックを詳しく見ると、この処理が実行されるのは特定のオプションが有効な場合のみでした。そして、それらのオプションが有効な環境を調べたところ、現在どのクライアント環境でも有効になっていないことが判明しました。
しかし、「現在使われていない」ことと「削除しても問題ない」ことは別問題です。過去に使われていた可能性や、今後使われる予定がある可能性も考慮する必要があります。

関係者への確認

そこで、コミットログから当時の開発者を特定し、さらに Slack を遡って当時の背景を知る人がいないか問いかけました。



両者に連絡を取り、この機能の経緯と現状について確認したところ、以下のことがわかりました:

  • バッチは認識通り削除して問題ない
  • Rails の Controller 自体は有効だが、実際にサービスサイト上のどこからも呼び出されていない
  • 今の hacomono では、マスタは物理削除ではなく論理削除を利用する方針に変更している

これで削除の判断材料は揃ったように思えましたが、念のため最後の確認を行うことにしました。

アクセスログの確認

「削除できる」と「誰も使っていない」の間には大きな隔たりがあります。思わぬところから API が呼び出されている可能性もゼロではありません。curl や postman を使えば叩ける API ですし、ごく稀に非常に古いフロントエンドのバージョンからアクセスされる形跡もあります。サービスサイトの API は非公開ですが、IT に詳しい利用者がサイトを解析して利用しているケースも、可能性は限りなく低いものの考えられます。
そこで、アクセスログを確認することにしました。hacomono では過去 90 日分のアクセスログが保持されているため、該当の API エンドポイントへのアクセス状況を調べました。結果は、90 日間で 0 件。これで確信を持って削除できると判断しました。

削除の実施

最終的に以下のコードを削除しました:

  • マスタレコードの before_destroy hook 相当ロジック
  • 管理サイト向け API Controller の該当エンドポイントとそのリクエスト RSpec
  • one-shot の Rake task とその RSpec

本来実現したかった RuboCop の指摘対応は、指摘されていたコード自体が消失し、解決しました。
結果として、1000 行のコードとその保守コストを削減できました。

教訓

今回のプロジェクトで得た教訓です。

テストコードはすぐに書こう

今回のケースでは、結果的にコードを削除することになりましたが、もしリファクタリングが必要だった場合、テストコードがないことは大きな障壁になっていた可能性があります。
テストコードは、安全にリファクタリングを行うための重要な投資であり、特に複雑な、あるいは至る所から依存されているドメインを扱う箇所では、その重要性がさらに増します。
加えて、後からテストを実装者以外が書くコストは想像以上に高いのは、経験した方であればわかると思います。AI にテストを書いてもらうことは可能ですが、コンテキストがない状態では、単にカバレッジを高めるだけの意味の薄いテストになる可能性があります。生成されたテストの内容や観点が妥当かを判断するには、実装時のコンテキストが必要不可欠です。
重要なのは、クラスやメソッドが必要になった時のコンテキストを実装者が忘れないうちにテストを書いておくことです。これは TDD 本や Certificated-Scrum Developer の研修カリキュラムに含まれる内容です。先にテストを書くのも良いでしょう。

補足…欠陥をテストとして再現することは有効

単にカバレッジを高めるだけのあまり意味のないテスト と尖った表現をしましたが、全くテストがないよりはマシなのも事実です。まずは書けるところだけテストを書いておくことを否定するものではありません。
また、万が一メソッドやクラスの問題が発覚したら、まず修正せず、先に問題を再現するテストを書いてテストを失敗させ、そこから修正する(AI にテストをコンテキストとして渡して修正してもらう)ことは有効です。これも TDD 本や「脳に収まるコードの書き方」、t-wada さんのブログにも記載のあるアプローチです。

RuboCop は厳しめから始めよう

後からルールを厳しくすることは、今回のように後続対応が必要になります。リソースやタイミングによっては、RuboCop の指摘に対処するという意思決定の合意を組織から得ること自体が、経験上非常に困難です。一方で、後からルールを緩和することは非常に容易です。
レビュープロセスにおいても、CI が通らなければマージできないというルールにするだけです。最初から適切な厳しめのルールを設定し、それを維持すると同時に、定期的にルールの妥当性を振り返り、チーム内でコミュニケーションを取ることが重要だと考えます。
現在のスキルでは、ルール緩和でしか対策できないと感じていても、実は回避できる書き方を知っているメンバーがいるかもしれません。ナレッジシェア・スキルトランスファーという観点でも良い影響があると思います。とっかかりが全くないならば AI に聞いてみるのも手です。

不要な機能やコードは早めに削除しよう

今回の削除作業には、当時の関係者への確認やログの調査などに、それなりの時間がかかりました。ただ、この時間が無駄だったとは思いません。hacomono の複雑なドメイン理解につながりましたし、入社間もない自分にとっては、越境的に普段縁のないメンバーと接点を持つまたとないチャンスでした。
それはさておき、使われていないコードを残し続けることは、保守コストの増加、新しいメンバーの混乱、そして技術的負債の蓄積につながります。テストと同様に、なぜこの機能が不要になったのかというコンテキストもどんどん揮発します。hacomono のようなベンチャーでは残念ですが定期的に退職者も出るため、そもそも機能を削除するという判断が容易にできなくなる可能性もありえます。
機能の削除は優先度が低いことが多いですが、個人的には機能開発に並ぶ重要な開発工程だと考えています。認知負荷を下げること… AI 開発時代においては、不要なコンテキストを削除することは、とても重要なプロセスになってきます。

ログを取ろう(ログの読み方を把握しておこう)

戦略的な機能削除でない場合、利用者が許容できるレベルで少ないか、全く利用されていないことを定量的に調べる手段が必要です。そのため、アクセスログを何らかの方法で保持することは重要です。
今回は、従来から利用している AWS の Athena だけでなく、SRE 部が整備してくれている Datadog も利用できたため、かなり楽に調査できました。

本質的な課題を解決しよう(そのためにコードを読もう)

RuboCop のコメントや RSpec のカバレッジといった定量的な数値だけを見て表面的に修正するのではなく、深くコードを読み込むことで、今回のようなデッドコードの発見につながりました。
特に入社して間もない時期にこのような課題に遭遇したのは、コードベースを理解する絶好の機会でした。
「なぜこのコードがあるのか」「このようなアーキテクチャになっている背景は何か」という疑問を持ち、疑問を疑問のままにせず、そのようになっている背景に興味を持つことが重要です。これはプロダクトエンジニアに必要な素養だと考えています。

おわりに

入社3ヶ月目でこのような貢献ができたことは、個人的に大きな自信になりました。
同時に、デッドコードが生まれてしまう背景には、ビジネスの変化、開発プロセスの課題、ドキュメントの不足など、様々な要因があることを実感できました。
プロダクトエンジニアとして、同じことを繰り返さないために——どのようにコードを書けばいいか、テストを書けばいいか、ドキュメンテーションはどうあるべきか——改善できることがあれば積極的に手を動かすマインドで日々開発をしています。(時には限界突破してしまうこともありましたが…)
これらの課題を一つずつ解決していくことが、より良いコード、より良いプロダクトにつながると信じています。


後書き

2009 年、私は渋谷マークシティ地下 1 階の啓文堂で「プログラマーのジレンマ」という本を手に取っています。
https://amzn.asia/d/are3UZn

OSAF Community によって Python で開発された OSS PIM「Chandler」の開発に伴う様々なエピソードを、ジャーナリストのローゼンバーグによって取材・執筆されたノンフィクション小説「Dream in Code」の邦訳です。
残念ながら書籍そのものは度重なる転居のタイミングで手放したようで、自宅の本棚にはもうありません。
ただ、この本の中に、プロジェクト管理の一環で、追加したコード行数を報告するというシーンがあったと記憶しています。
そこで、登場人物——具体的な人物名は忘れてしまったのですが、だれかが、不要なコードを見つけて削除し svn でコミットします。
その際に、追加したコード行数にマイナスの値を報告した、というエピソードに、強烈なインパクトを受けたことを覚えています。
今回、追加行数にマイナスの値を報告できる喜びを、15年の時を超えてチャンドラー開発チームと共有できたことを嬉しく思います。

*1:大量のコードベースから当該箇所の特定を Grep のみで実施するのは骨が折れるため、 Cursor と Claude 4.5 Sonnet を使って詳細に調査してもらいました。執筆時点の11月であれば、リサーチや仕様検討に強いモデルである Codex を利用すると思います。