システム開発の業務に携わっていると、「レガシーコード」に関わる機会もあるかと思います。
プロジェクトによっては、品質の高いシステムに携わるケースとそうでないケースに分かれるかと思います。
私自身、障害が発生しにくいシステムを提供するため「レガシーコード」への向き合い方を考えることがあったので、本記事を書いてみました。
お断り
ここから書くことは、いわゆる何らかの「ベストプラクティス」に沿ったものではなく、個人の見解となります。
そのため、誤りが含まれる可能性もあるので、そういった点があればご指摘いただけますと幸いです。
レガシーコードとは
一般的に、あるべきアーキテクチャに沿っていない・改修により不具合が発生しやすい・テストコードがないといったコードがレガシーコードと呼ばれるケースが多いかと思います。
ChatGPTにも「レガシーコードとはなんでしょうか。」と聞いてみると、以下の通り返ってきました。
※一部のみ抜粋
- 保守が困難(コードが複雑で可読性が低い・ドキュメントが不足している・コードを書いた人が既にいない)
- テストがない(ユニットテストや自動テストが整備されていない・変更するとバグが発生する可能性が高い)
- 技術的負債が多い(古い技術やフレームワークが使われている・モダンな開発手法と合わない(例:オブジェクト指向でない、スパゲッティコード))
- 依存関係が強い(他のコードと密結合していて、部分的な変更が難しい・レガシーなライブラリや環境に依存している)
レガシーコードが発生する背景
例えば、以下背景があるかと思います。
- 初期のアーキテクチャ検討が不十分だった。
- 技術力のあるメンバーによるレビュー体制が整っていなかった。
- コードレビューを行うフローが存在しなかった。
- 開発者が足りず、品質を意識したコードを書く余裕がなかった。
- ビジネスの都合上、とにかく早くローンチが必要だった。
理想は、余裕のあるスケジュールを組んだうえで技術力のあるメンバーのいる体制で開発することかと考えています。
そして、アーキテクチャ検討段階から丁寧に進め、工程ごとに都度手厚くレビューを実施することで、品質の高いシステムの提供が期待できます。
ただ、実際には開発者が足りない・納期が厳しいといった事情で上記の実施が難しいこともあるかと思います。
そういった場合、改善の余地があるコードが発生し、レガシーコードを含んだシステムになる可能性が出てきます。
保守・エンハンスの上で、PLの立場で注意すること
とはいえ、レガシーコード脱却のためのシステム刷新といった大きなプロジェクトを組むのも難しいケースは多いかと思います。
その中で、既存システムの保守・エンハンスをどう進めたらよいかを考えていきます。
なお、私は開発者ではなくPLの立場で仕事をしています。
そのため、あくまでもPLの立場での向き合い方を書いてみます。
リファクタリング実施要否を慎重に判断する
「レガシーコード」なので、コードレビューをしている段階でリファクタリングを実施してもらいたいことも出てくるかと思います。
しかし、リファクタリングによって他機能の不具合が発生するといったリスクが潜んでいます。
要件によっては、例えば金融系のシステムなど障害発生時の影響度が大きいシステムもあるかと思うので、特にリファクタリングによって不具合が発生しないかは慎重に判断する必要があります。
例えば、不要と見受けられるメソッドが残っているケースがあるとします。
こういったメソッドはしばらくテストされていない可能性も考えられるので、呼び出し処理を誤って書いてしまい不具合に繋がる可能性があります。
そのため、不要なメソッドは極力残さないようにする必要があります。
ただ、自分が開発者として仕事をしていた時期に、一見不要に見えたメソッドを削除したが、実はそれを呼び出している箇所があったというケースがありました。
IDEの機能では呼び出しされていないようだったので不要そうと判断してしまったのですが、IDEで検知しきれない階層のコードで使っていたというオチでした。
この場合だと、IDEの機能だけに頼り切らず、全階層を対象にメソッド名で検索も行い、本当にどこでも使われていないかを確認する必要があります。
また、本来の目的の改修(機能追加など)と同タイミングでリファクタリングをしたいという声が開発者から上がる場合もあるかと思います。
その場合、リファクタリングによりどの機能まで影響するか・そういった機能のテストが十分可能かを検討したうえで実施要否を判断する必要があります。
もし広い範囲へ影響し得る場合、本来の改修とは別タイミングで実施する・そもそもリファクタリングを実施しないといった方針も視野に入れます。
レガシーコード特有の注意ポイントをノウハウとして蓄積する
システムによっては、ベストプラクティスに沿っていない設計も存在するかと思います。
そういった設計が存在することが意識から漏れていると、改修によりその設計が影響して不具合が発生するケースもあります。
また、改修後はコードレビューを行うケースが一般的かと思いますが、特にプロジェクトに参画して日数が経たないメンバーがレビューする場合、たとえそのメンバーが開発経験が長かったとしても上記設計への意識が足りずに指摘漏れが発生する可能性もあります。
そのため、そういった注意ポイントを内部Wikiなどにノウハウとして蓄積していくことが対策としてあげられます。
すぐに網羅的にまとめるのは難しい・ノウハウを読んでもそのことを忘れてしまうといった課題は考えられますが、チーム内でノウハウ共有できる状態にはなるので、システム特有の設計に起因した不具合はある程度でも減らせることが期待できます。
例えば、JavaScriptで変数定義を行うにはvar,let,constを使います。
そのうち、varは一般的に使わない方が良いと言われます(参考:https://qiita.com/ist-t-s/items/49ed558bd837452353b8 )。
そのため、既存でvar宣言している実装を見つけたらletやconstへと直してもらうよう指摘したくなります。
ただ、もしそのvar宣言を例えばループで複数回行うような設計となっていたら、再宣言が実行されるためエラーとなってしまいます。
なので、「var宣言は基本的に修正しない。修正する場合、再宣言が実行されないかを確認する。」といったノウハウを残しておきます。
また、意図があってvar宣言していると分かったら、その意図をコメントとして追記しておくとより親切です。
ただ、こういったノウハウを残しても、長期間目を通されないとどうしても忘れられてしまいます。
そのため、定期的に読み合わせの時間を取るのも良いかと考えています。
テストコードの作成計画を組む
レガシーコードはどうしても修正による意図せぬ不具合が発生する可能性が出てきます。
前提として、不具合が発生しないよう注意を払いますが、気づくのがどうしても難しいケースも出てきます。
そのため、不具合検知を機械に任せられるよう、徐々にでもテストコードを整備していくのが有効と考えています。
テストコードがないと、どうしても打鍵テストのみで品質担保する必要がありますが、場合によっては例えば影響度の大きい不具合の修正リリースが必要になった場合、網羅的にテストを実施できない可能性も出てきます。
なお、自分の担当外のプロジェクトにはなりますが、そういったリリースの前に、修正時に発生した別の不具合の検知がテストコードでできたケースもあります。
ここで、全くテストコードが整備されていない場合、どこから着手するかという問題はあります。
その場合、個人的な意見ですがレガシーコードの場合はITから整備するのが良いのかなと考えています。
- UTに比べて相対的に短期間で全機能をカバーできるテストが書けるはず。
- UTが書きにくい(一つのメソッドに条件分岐が大量に含まれているなど)設計になっているケースも考えられる。
とはいえ、ITのみだけだとどうしても検知できないケースはあるので、並行して徐々にでもUTを充実させるのが良いのだろうなと考えています。
ただ、レガシーコードの作り上、どうしてもテストコードが書きにくいことも多いかと思います。
また、どういった戦略で整備していくかも整理しないと、開発者によって品質に差が出てくる可能性もあります。
こういった課題に対するアプローチは私自身見つかっていないので、機会があれば記事にしてみたいなと思います。
まとめ
ここまでで、レガシーコードに向き合うにあたり気をつけるべき部分を書いてみました。
といいつつ、自分も実践しきれていない部分も多く、また過去に指摘いただいた内容も含まれています。
また、開発体制上、実践しきるのが難しい部分も多々あるかと思います。
ただ、こういった意識を持っているか、全く持っていないかというだけでも、向き合い方が大きく変わってくるとも思っています。
より安定稼働するシステムの提供のため、引き続き努力していきたいと思います。