pong.ursm.jp

メンテナブルなシステムのための基本戦略

最初はよい感じだったはずのシステムがなぜ頭痛の種になってしまうのか、どうやったらその流れに逆らえるのかをずっと考え続けてきた。辿り着いた結論はこうだ。

コードが増えるとメンテナンスしにくくなる。コードを減らせばメンテナンスしやすくなる。

馬鹿みたいだが、コードベースを小さく保つのは本当に大事なことだ。調査していたコードがすでに使われていないとわかったときの徒労感は誰しも覚えがあるだろう。同じことをしているはずなのに微妙に異なるコードがいくつも見つかってイライラした経験もあるはずだ。

私の主張はこうだ。コードベースを小さく保つことは、無数にあるやったほうがいいことのひとつではなく、メンテナブルなシステムを実現するための必要条件だ。ひょっとしたらエレガントな設計や網羅的なテストスイートや型システムの導入よりも大事かもしれない。迷ったときやよくわからないときに選んでいい、それぐらいの優先度がある。

なぜ見過ごされるか

重要性は誰もが認める。しかし実際にコードを積極的に減らしているチームは多くない。なぜか。

理由のひとつはエンジニアリングが「新しく作ること」と同一視されがちなことだ。コードを消す、機能を削る、仕組みをシンプルにする、こういった作業は地味で、技術ブログのネタにもなりにくい。派手なアーキテクチャの刷新は発表のタイトルになるが、不要なコードをこつこつ消し続けていることを話題にする人はあまりいない。

もうひとつは日々の忙しさだ。やったほうがいいとわかっていても、目先のリリースに追われているとリファクタリングはTodoリストに入ったまま日の目を見ない。そのまま時間が経つと、いざ手をつけようとしたときには対象のコードへの理解が薄れてしまっていて、何をしていたコードなのかを理解するところから始めなければならない。コストが雪だるまになっていく。

コードのメンテナンスは延命にすぎず、根本的な対応は特別なイベントとして行うものだという認識もある。しかしそれは逆だ。日々当たり前にやるべきことであって、できていないから特別なイベントになってしまう。

コードベースを小さく保つとはどういうことか

コードベースを小さく保つとは、いらなくなったコードが残っていない状態を維持することだ。使われていない関数、到達しない分岐、かつての仕様の名残で残っているモデルやテーブル、こういったものが積み重なってコードベースを不必要に大きくしていく。

小さなコードベースがもたらす利益は複利的だ。grepしたときにヒットする行数が減り、テストの実行時間が短くなり、変更の影響範囲を見通せるようになる。多少設計がまずかろうがバグがあろうが、全体のコード量が少なければ大抵のことには対処できる。同じバグでも対象のコードが100行、1画面に収まる程度なら直すのに1日も掛からないだろうし、最悪でも一から書き直せる。これが1,000行や10,000行になると途端に話が難しくなってしまう。

差分を最小化するのではなく全体の複雑性を最小化する

温泉旅館を思い浮かべてほしい。必要に応じて増築していった旅館は、廊下を歩いていると階段が突然現れたり、棟と棟のつなぎ目に不思議な段差があったりする。個々の増築はそのときの判断として合理的だったはずだ。既存の建物に手を加えず、最小限の変更で部屋を足していった。しかし積み重なった結果として、誰も意図していなかった迷路ができあがる。

コードベースも同じだ。「既存のコードになるべく手を加えない」という一見謙虚なポリシーが、長期的には全体の複雑性を増大させる。

このポリシーはOSS開発の文脈では正しい。コントリビューターは対象のコードベース全体への理解が限られているし、変更の影響範囲を把握しきれないから、余計なところに手を出さないのは妥当な判断だ。問題はそのやり方を仕事にもそのまま持ち込んでしまうことにある。

自分たちのコードベースで日々開発している我々はコントリビューターではなく、コミッターとして振る舞わなければならない。場合によっては全体に波及するような変更を躊躇してはならないし、それができる立場にいる。変更を加えるとき、同じパターンがコードベース全体に存在しないか確認する。できるなら全体に適用する。新しく追加したコードが、まるで元からそこにあったかのように周囲と調和している状態が理想だ。

実践:いらなくなったコードをその場で消す

なにはなくともこれだけは絶対にやろう。

コードがいらなくなった瞬間を逃すとあっという間に既存のコードに紛れてしまい、後からそのコードが使われていないことを確かめるのはしばしばとても困難になる。使われていないコードは壊れることがないので直されることもなく、現役のコードとどんどん乖離していく。やがてシステム内に矛盾する複数のコードが存在することになり、何か変更を加えようとするたびどれが正なのか都度調査する羽目になる。

コードをばっさり消すのは不安だという心情も理解できる。しかし残すことは単なるリスクの後回しというだけではなく、将来的に何倍にもなって跳ね返ってくる地雷を埋める行為だ。今潰せるリスクは自分で責任を持って潰そう。もちろん作業見積もりにはその分の時間を加味するべきだ。

どうしても消せないなら、せめて消せない理由をコメントとして残そう。TODO だけでは不十分で、どうやったら消せるようになるのかがわからないといけない。「Rails 8 へのアップデートで不要になったはずだがちゃんと動作確認できていないので残しておく」と書いてあれば、そのコードを消した状態で動作が変わらないことを確認すれば消せるとわかる。

コードをコメントアウトして残すという手もある。消さないでおくよりは遥かにマシだ。少なくともそのコードが現役ではないことが一目でわかる。ただ、コメントアウトできるということはそのコードがなくても動くことは明白なのだから、特段の理由がない限り消したほうがよい。

実践:コードを書かない判断をする

いらなくなったコードを消すことと並んで重要なのが、そもそもコードを増やさない判断だ。

まず仕組みを早まって作らない。「将来こうなるかもしれない」という予測のもとに作った抽象化やフレームワークもどきは、その予測が外れた瞬間に負債になる。本当に必要になったときに作れば十分で、そのときには何が必要かもずっとはっきりしている。

仕様をコードに合わせて提案することも有効だ。ビジネス上本質的でない箇所、例えばエラーメッセージの文言や細かい表示の仕様は、実装上都合のよい形に積極的に誘導してよい。顧客やプロデューサーに任せると実装コストを度外視した仕様になりがちだが、作る側が提案すればその分のコードを書かずに済む。どう実装されれば都合がよいかを知っているのは自分たちだけだ。

そしてマイクロサービスのような大仰な仕組みを安易に採用しない。シンプルな問題をシンプルに解くことができているなら、それは十分に優れた設計だ。損益分岐点を超えていない複雑さを持ち込んでも、維持するコストだけが増える。

まとめ

コードベースを小さく保つことは、地味で目立たない。新しいアーキテクチャを導入することでも、テストカバレッジを上げることでも、型システムを整えることでもない。使われていないコードを消し、余分な仕組みを作らず、変更を加えるたびに全体が少しずつ整っていく状態を維持することだ。

それでもこれを最優先の指針として選ぶ理由がある。多少設計がまずくてもコードが少なければ対処できる。逆にどれだけ設計が美しくてもコードが膨れ上がれば身動きが取れなくなる。コードの少なさはあらゆる問題への対処能力を底上げする、いわば基礎体力だ。他にやるべきことが山積みでも、これだけは手を抜かないでほしい。