ビジネス要件の変化で、それまで本番で使っていた機能が不要になることがたまにあります。
私も先日、不要な機能をコードベースから削除する必要があったので、その機能に使われているコードを余すことなく把握するベストな方法は何かを改めて考えて色々試してみました。
結論からお伝えすると、1) 削除する機能の初期実装を確認する、2)初期実装後に追加・変更されたコードを確認する、という流れが効率的だったので、それぞれ詳しく解説したいと思います。
初期実装を確認する
削除対象のコードを把握するには、開発したブランチでの初期実装を確認するのが一番効率が良いと思います。機能にもよりますが、筆者個人の実感としては削除対象コードの80%ぐらいはこの方法で把握出来るイメージです。
プルリクエストを探す
初期実装を確認するのに最も手っ取り早いのは、その実装の過去のプルリクエストを見てどのようなファイルやコードが追加されたのかを確認することです。
タスク管理ツール上でその機能を実装したタスクの説明欄やコメント欄にプルリクエストリンクが含まれていることが多いので、まずはそこを探します。
もしタスク上になければ、githubなどのソースコードレポジトリーの過去のプルリクエスト一覧から探します。
いずれかの方法で当時のプルリクエストが見つかれば、後は追加されたコードを確認して削除対象コードをリストアップするだけです。
開発ブランチを探す
もしプルリクエストが見つからなかったり、そもそもプルリクエストを作成しないでローカルでブランチをマージしていた場合は、今度は開発ブランチが残っていないかを確認します。
私の場合、以下のコマンドで簡単に検索出来るように、ブランチ名に必ずタスクIDを含めるようにしています。例えば、feature/12345-search-postsのような感じです。
git branch --all --list '*task_id*'
もしブランチが見つかった場合、そのブランチに含まれるコミット一覧をgit logで取得できます。
git log ブランチ名
ただ、これだともし開発ブランチに定期的にmainをマージしていた場合、コミットヒストリーに他の開発ブランチからのコミットが混ざってしまいます。
そういったケースも想定して、私はコミットメッセージに必ず開発ブランチ名を記載するようにしています。そうすることで、関連コミットのみをフィルタリングすることが出来るからです。
git log --grep=branch_name --no-merges
なお、正確なブランチ名はわからないけどタスクIDだけはわかるという場合は、上記コマンドでブランチ名の代わりにタスクIDを使って検索することでも同様の結果が得られます。
ブランチが削除済みなど、ローカルレポジトリーで見つからない場合は、削除予定の機能に使われているファイルに対してgit blameを使うことで、そのファイルがどのコミットで作成・変更されたのかを調べます。
git blame file
git blameでコミットが見つかったら、git showを使ってコミットメッセージに含まれる開発ブランチ名を割り出すことが出来ます。
なお、もしコミットメッセージにブランチ名を記載していなかったら、以下のコマンドで開発ブランチを探してみます。
git branch --all --contains commit_id
削除対象のコードを確認
開発ブランチなどで関連コミット一覧が見つかったら、それぞれのコミットに含まれる差分コードをgit showで確認します。
git show commit1 commit2 ...
これで、初期実装のコードはほぼ間違いなく把握出来るはずです。
以上のことを踏まえると、以下の基本事項を徹底するメリットが改めて明確になりますね。
・開発ブランチを作ること。
・コミットメッセージに開発ブランチ名を記載すること。
・プルリクエストを作成すること。
チーム開発では上記は自然に行えていることが多いですが、個人開発だとレビューワーがいないため、私もプルリクエストを作成せずにそのままmainにマージしてしまうことがほとんどです。機能削除の際の調査の容易さを考えると、個人開発でもプルリクエストを作成するメリットは十分にありそうです。
初期実装後の追加・変更箇所を調べる
もし同じ機能の改修などで、改修タスク、プルリクエスト、開発ブランチが作成されている場合は、上記で説明した初期実装を調べるのと同じ方法で変更コードを把握出来ます。
ただ、直接その機能の改修を行ったのではなく、他の機能の開発タスクによって変更されていた場合、該当プルリクエストやブランチを探すのはかなり難しくなります。また、仮に見つかったとしても、含まれるコードの大半は削除予定の機能のコードとは無関係なので効率が悪いです。
そのような場合、私は、LSPとgrepで初期実装以外に削除対象のコードがないかを調べています。
まずLSPですが、大抵は定義された関数や変数がどこで使われているかを調べる参照機能がついているので、それを使って削除予定の機能で定義されていた関数や変数がアプリケーションの他の部分で使われていないかを調べます。
同様に、今度はgrepを使って、削除予定の関数や変数、翻訳ファイルの文字列が別の箇所で使われていないかを調べます。関数や変数などはLSPと同様の結果となり重複することが多いですが、LSPで見つからなかったものがgrepで見つかることもあるので、両方のツールを使うようにしています。
コードを削除する
これまでのプロセスで削除コードを把握出来たら、実際にコードを削除し、自動テストを実行してみます。テストカバレッジがある程度あることが前提ですが、テストが全てパスすれば当該コードを削除しても主要な機能は問題なく動いているということなので、リリースしても大丈夫だろうという確信を強めることが出来ます。
なお、プルリクエストを作成してレビューしてもらうのはもちろんですが、私は機能削除タスクであってもその機能が確実に削除されたこと、関連する機能に影響がないことをより確実にするため、ステージング環境でチームの他の人にテストしてもらうようにしています。
コードレビュー、ステージング環境でのチェックもパスしたら、mainブランチのマージして本番にリリースします。なお、本番リリース後は無事にアプリケーションが動いていることを自分で触って確認することに加え、エラー検知ソフトを使って削除したコード関連のエラーが出てこないかをチェックします。(個人的な体験だと、削除したコードを使っていたcronジョブを消し忘れていたことがありますが、エラー検知で無事にキャッチすることが出来ました。)
まとめ
不要になったコードを削除すると、保守範囲が小さくなりリグレッションを気にする必要もなくなります。使われていないけど、もしかすると将来的に使うかもしれないから取っておこうとか、削除して万が一本番に影響があったらいやだからそのままにしておこうなどどせずに、不要になったらすぐに削除してすっきりしたコードベースをメンテすることを心がけたいものです。
