nunulkのプログラミング徒然日記

毎日暇なのでプログラミングの話題について書きたいことがあれば書いていきます。

レガシーコードと出会ったときにどう振る舞うか

この記事について

「レガシーコード」とは、「保守または拡張が困難な既存のプロジェクト」(レガシーソフトウェア改善ガイドにおける定義を流用)で書かれたコードを指します。場合によっては難解なコードに対する悪意を込めて「クソコード」などと呼ばれたりしますが、個人的にはこの名前が好きになれないので、本記事ではそういう含意があったとしても「レガシーコード」で統一します。

というわけで本記事では、レガシーコードに出会ったときに自分がこれまでどう振る舞ってきたか、これからどう振る舞っていきたいか、について備忘録がてらまとめておこうと思います。

レガシーコードとは

legacy という言葉の一般的な意味(遺産)が示すように、レガシープロジェクトは通常、前の開発者またはチームから引き継がれる。言い換えると、そのコードを最初に書いた人々と、いまそれを保守している人々は、同じではない。それどころか、間に何世代もの開発者が挟まっているかも知れない。したがって現在の保守担当者には、なぜコードが、そのような仕組みになっているのかを知るすべがなく、しばしば、それを書いた人々の意図と、設計上の暗黙の想定とを、推理する必要に迫られる。

クリス・バーチャル. レガシーソフトウェア改善ガイド (Japanese Edition) . 翔泳社.

私はキャリアの中で(とくに前半)いくつかのレガシープロジェクトに関わったことがあります。そのほとんどが、自動テストがない、コードが読みにくい、ドキュメントもない、の三拍子揃っているのが特徴で、下手に変更を加えると予期せぬところでエラーが出る、みたいなことが日常的に起こります。もちろん、レガシープロジェクトであっても、品質が高く、生産性もそれほど悪くない、みたいなケースもあるかと思いますが、少なくとも自分の経験ではそういうケースはありませんでした。

最近ではスタートアップに関わることが多いですが、意外と最初からテストコードがあり、ドキュメントも必要最低限書かれているような、優良なチームと関わることが多いです。全体的にそうなのか自分の関わるところがたまたまそうなのかは分かりませんが、最近の若者はしっかりしてんなーと思います。

なので、これは多分に偏見を含むと思いますが、(自分が50代なのを棚に上げて)比較的年齢が上の人たちのほうが、レガシーコードを生みがちであるなぁという印象を持っています。こと PHP に関しては、言語やフレームワークの進化についていけず、昔ながらのやり方を変えずに不具合を量産しているベテランが多い印象もあります。

これまでレガシーコードに出会ったときにどうしてきたか

典型的かつ極端な例として、とあるプロジェクトでの経験を元に、レガシーコードに対してどう対処したか、その結果どうなったか、というのを紹介します。

上手くいったケース:文化的に理解があり影響範囲が限定的かつコントロール可能なケース

1つ目は、文化的に理解があり影響範囲が限定的かつコントロール可能なケースです。ほとんどの仕事は途中から参画しますが、既存プロジェクトであっても新規の機能開発だったり、既存の機能の拡張であったり、なにをやるかはまちまちです。新規の機能開発の場合はわりとやりやすくて、自動テストがない場合はテスティングフレームワークの導入からやり、その機能に対して最初からテストコードが書けます。しかし、まずは自動テストを書くことを許可してもらう必要があるので、現場の社風だったり、マネジャーの価値観だったりによって、すんなり認めてもらえるか説得不可能かが決まります。数年前から自動テストのない現場の仕事は請けないようにしているので、最近は自動テストが存在しないプロダクトには関わっていませんが、昔は自動テストが書かれているプロダクトなんてほとんどなかったので、自分で導入するしかなかったケースもありました。環境さえ整えば、それに乗っかってテストを書く人も現れます(現れることもあります)。

上手くいったケースでは、テストを導入してくれて助かった、とか、あとから引き継いだ人に nunulk さんのコードはテストもあるし手を加えるのが楽だった、とか、感謝してもらえることもあります。

上手くやるには大前提として、マネジャーやチームからテスティングフレームワークの導入を歓迎される必要があります。自動テストを書いたことがないメンバーには、どうすればテスタブルなコードが書けるか書き方の手本を見せてやる必要があります。

最初からぜんぶのテストコードを書くのではなく、徐々に増やしていく地道な作業になります。機能の重要度やロジックの複雑度などによって優先順位をつけ、メンバー全員で(少なくとも何人かは)力を合わせて取り組む必要があります。一人でやっても自分の書いたコードの品質は上がりますが、プロダクト全体からみると一部にしかなりません。

上手くいかなかったケース:チームの中に導入したい人はいるが、難易度が高すぎるケース

私はここ数年は Laravel の仕事しかしないようにしていますが、その理由の1つとして、テストが書きやすいというのがあります。詳しくは省きますが、Laravel が PHPUnit を拡張し、テストコードが書きやすいように手を加えているのです。

大規模なレガシープロジェクトでも、自動テストを部分的に導入することは可能ですが、使っているフレームワークによっては、ほとんどテストが書けない場合があります(ありました)。

その現場ではとある古いフレームワーク(バージョン 0.x でα版でした)を何年も使っていて、フレームワーク自体が自動テストのことをまったく考慮しておらず、基盤を作るところから始めなければなりませんでした。また、アプリケーションもコピペが蔓延していたり、静的メソッドが大量にある巨大なクラスがあったり、テスタブルでない構成になっていて、リファクタリングしないとテストが書けない、テストがないとリファクタリングできない、というウロボロス状態でした。

マネジャーもどちらかというとテストコード書くくらいならプロダクションコードを書いてくれ、という感じの人だったので、けっきょく当時の自分のスキルでは難易度が高すぎて断念してしまいました。

メンバーと話すと、何人かは自動テストほしいですねぇという人はいたんですが、やはり同じように難しさを感じていたり、いままでこうやってきたから、みたいな諦観があったりしたように思います。

これがけっこう曲者で、信念を持って自動テストは要らない!という人に対しては、あ、そうですか、ではさようなら、で済むんですが、テストほしいですねぇ(だがやらない/できない)という人に対しては、書けるようになってほしい、という思いが出てきてしまいます。そこが自分の良くないところではあるなぁと思うんですが、けっきょくのところ、ほしいですねぇで止まってしまう人というのはそこまで欲してないわけで(欲していればやりますよね?)、他人にレールを引いてもらわなければやらない人に対して、お節介を焼くのはどうなんだ?っていう思いもあります。

これからレガシーコードに出会ったときにどうしていきたいか

これを言うと身も蓋もないんですが、まずは出会わないようにすることが第一だと思っています。テストがないのが当たり前、の世界観で長く働いてきたマネジャーやメンバーに対して自動テスト導入の心理的ハードルは非常に高いと感じます。いい悪いの話ではなくて、常識が違いすぎるのです。

相手の価値観を変える労力を考えると(しかもそれは本質的に必要な労力ではないと思いますし)価値観が一致するチームで働くのが楽です。いままでもそうですが、事前の面談で必ず自動テストはありますか?と聞き、ない場合は基本的にお断りするようにしていきます。

とはいえ、事情によりレガシーコードの面倒を見ることになることもなくはないと思うので、そうなったときにどう振る舞うべきか、というのは考えておく必要があると思います。

少なくとも、責任者にテスティングフレームワークの導入自体は認めてもらいたい、そこを最低限確保できないならどんなしがらみや報酬があっても断る、というのを原則にして、自分自身はテストコードを書く、他のメンバーには強制しない、書きたい人には教える、というスタンスでいこうと思います(いままでとほぼ変わらないですが)。

プロダクトの内部品質については、マネジャーの意向が大きく反映されますがそれがすべてではなく、チーム全体のマインドセットやスキルセットが大きく関わります。スキルは伸ばせばいいだけですが、マインドを変えるのは(相対的に)難しく、それは経験のある人ほど難しいのではないかと思います(統計的にどうなのかはわかりませんが、体感的に)。

大事なことなので二度書きますが、いい悪いの問題ではなく価値観の違いの問題だと捉えたいのです。

自分が正義だと考えてしまうと、テストコードを書かない人が悪になってしまい、悪を滅ぼすことが目的になってしまいます。ですが、たとえ不具合だらけであってもレガシーコードがサービスを動かし、会社に売上をもたらしていることは事実であり、その事実こそが正義だと思うのです。どんなコードであれ、世の中に価値を提供できるコードが善である、という前提は忘れないでおきたいし、仮に「エンジニアが音信不通になっちゃって…」みたいなケースであっても(実際ありました)、どの方角かはわかりませんが、前任者に向かって手を合わせたい、という気持ちはあります(実際にはしませんが)。

既存のコードをリスペクトするっていうのは大前提ですが、それはそれとして、自分のあとを引き継ぐまだ見ぬ人たちに対しては、少しでも「保守または拡張が困難な」部分を減らしたい、関わるプロダクトに対しては、市場の変化に迅速に追従できるような柔軟なソフトウェアにしたい、という思いは持ち続けたいです。

「諦めたらそこで試合終了ですよ」っていう安西先生の名言(スラムダンクは読んだことありませんが)を忘れずに、ホイッスルが鳴るその瞬間までベストを尽くしていく所存です。

おわりに

他人のマインドを変える労力は本質的に必要なものではなく、相手がベテランであればあるほど難しい、このことを踏まえると、相手の価値観は尊重し、業務委託である自分はプロダクト全体の品質向上に対する責任はないので、自身の仕事を全うすることに注力したほうがいい、という結論に自ずとなる気がします(なりました)。私の長所でもあり短所でもあるのですが、プロダクトやチームに対する熱量が高いために、ときに自分の責務を越えて手や口を出してしまう傾向があって、それが良い結果になることもあれば悪い結果になることもあるんですが、自分も相当なベテランなので、その辺もうちょっとうまくやればいいのに、とことあるごとに思います。

まだ精進が足りないですね。