ブログ
Boring Engineering #3:コードを共通化すれば、本当に保守しやすくなる?
< なぜ、この話が重要なのか? >
長く使い続ける業務システムでは、機能の追加や変更に必要な手間や費用が、ソフトウェアの設計によって大きく変わることがあります。
今回は、10年以上運用されているWebアプリケーションでの実例を通して、将来の変更に強いソフトウェア設計について考えてみます。状況
最近、長年運用されているWebアプリケーションに、新しい機能を追加する機会がありました。
システム管理者と一般ユーザーの両方が、同じ通知設定を、それぞれ異なる立場から変更できるようにする機能です。
システム管理者は、複数の拠点を管理します。そのため、一覧表示、コンテキストメニュー、モーダルダイアログなどを備えた、多機能なCRUDインターフェースを使用しています。
一方、一般ユーザーが扱うのは、自分の拠点だけです。画面も比較的シンプルで、特定の機能に特化した全画面の入力フォームが中心です。
どちらの画面にも、基本的には同じ新機能を追加する必要がありました。
当初は簡単な作業に思えました。しかし、実際に実装してみると、両者の難易度には予想以上の違いがありました。
共通フレームワーク
10年以上前、このシステムが最初に開発されたとき、管理画面には汎用的なCRUD(Create・Read・Update・Delete)フレームワークが採用されました。
画面ごとに個別の処理を実装するのではなく、一覧表示、ダイアログの表示、データの編集・保存などの仕組みを共通化することで、一貫性のある管理画面を効率よく構築できるようにしたのです。
当時としては、合理的な選択でした。
しかし、システムが長年にわたって進化する中で、当初の設計では想定されていなかった要件が次々と生まれました。
今回の新機能でも、共通CRUDフレームワークには、必要なコンテキスト情報の一部を受け渡す仕組みがありませんでした。
その情報はフロントエンド側では取得できていましたが、フレームワークを通してバックエンドのコードへ渡すことができなかったのです。
新しい要件に正面から対応するには、共通フレームワークの複数の層に手を加える必要がありました。

【図1:共通CRUDフレームワークを経由する管理画面の構成】
しかも、このフレームワークはアプリケーションのさまざまな機能で利用されています。
変更によって、今回の通知設定とは関係のない機能にまで影響が及ぶ可能性があります。そのため、小さな修正であっても、作業が複雑になり、リスクを伴うものでした。
現実的な回避策
そこで、共通フレームワークを変更せずに済むよう、既存の「ホワイトボード(Whiteboard)」という仕組みを利用しました。
不足しているコンテキスト情報を、フレームワークを経由せず、アプリケーションへ直接受け渡す方法です。
これにより、変更範囲を限定することができました。

【図2:ホワイトボードAPIを使い、共通フレームワークを迂回する構成】
ただし、この回避策にも代償がありました。
リクエストハンドラとアプリケーションロジックの間に、暗黙的な依存関係が生まれたのです。
情報がフレームワークの通常のインターフェースではなく、別の経路を通るようになったため、データの流れの一部が分かりにくくなりました。
共通部分への変更リスクは避けられましたが、その代わりに、処理の見通しが少し悪くなったわけです。
専用インターフェース(The Dedicated Interface)
一方、一般ユーザー向けの画面は、事情がまったく異なりました。
こちらは、通常のHTML、CSS、JavaScriptと、機能ごとに用意されたリクエストハンドラで構成された、単一機能専用の全画面UIです。
管理画面とは異なり、共通フレームワークには依存していません。
フロントエンドからアプリケーションのビジネスロジックへ、直接情報を渡すことができます。
そのため、新しい設定項目の追加は比較的簡単でした。変更がその機能の内部だけで完結したからです。

【図3:フロントエンドとバックエンドが直接つながる専用画面の構成】
もちろん、この方式では、機能ごとに専用のコードを書く必要があります。
共通フレームワークなら自動的に提供される処理も、自分で明示的に実装しなければなりません。
しかし、共通フレームワークへの依存を考慮する必要がなく、無関係な機能に影響を与えるリスクもありませんでした。
結果として、専用の実装を持つ一般ユーザー向け画面のほうが、はるかに修正しやすく、処理も理解しやすかったのです。
この経験から、一つの疑問が浮かびました。
コードを共通化すればするほど、ソフトウェアは本当に保守しやすくなるのでしょうか?
コード再利用に潜むコスト(The Hidden Cost of Reuse)
コードの再利用には、明らかなメリットがあります。
重複する作業を減らし、動作の一貫性を保ち、一つの改善を複数の機能に反映できます。
特に、システムの初期開発では大きな効果があります。
しかし、共通コードを利用する機能が増えるたびに、その機能は共通コードの動作や将来の変更にも依存することになります。
つまり、共通コードを使う機能が増えるほど、そのコードを変更したときの影響も大きくなるのです。
そして、10年以上にわたって要件が変わらないことは、ほとんどありません。
業務ルールは変化します。設備も変わります。新しい利用方法が生まれ、システムの設計当初には誰も想定していなかった機能を、お客様から求められることもあります。
そのような変化の中で、当初は開発を効率化してくれた共通フレームワークが、やがてソフトウェアの進化を妨げる要因になる場合があります。
だからこそ、何を共通化するかを慎重に選ぶ必要があります。
安定した汎用的な仕組みは、共通化に適しています。
一方、個別に変化していく可能性のあるアプリケーション固有の処理は、専用コードとして実装したほうが保守しやすい場合があります。
では、その境界線をどこに引けばよいのでしょうか?
一つの判断基準として、次のように考えられます。
単にコードが似ているからではなく、同じ理由で変更されるコードを共通化する。
どちらの方式が適切かは、要件や、システムが今後どのように進化していくかによって異なります。そして、その判断基準自体も時間とともに変わる可能性があります。
長年にわたって運用され、進化し続けるソフトウェアにとって、保守性とは単にコードの重複を減らしたり、行数を少なくしたりすることではありません。
将来の変更を理解しやすく、影響を予測しやすく、そして変更範囲を適切に限定できることも重要です。
優れた設計とは、コードの量を最小限にする設計ではなく、将来の変更が及ぶ範囲を最小限に抑えられる設計なのかもしれません。
< お客様にとっての意味 >
お客様にとって重要なのは、ソフトウェアの内部構造そのものではなく、業務の変化に合わせて、必要な機能を無理なく追加・変更できることです。
目先の開発効率だけでなく、将来の改修コストや運用への影響まで考えて設計する。それが、長く使い続けられるシステムをつくるための大切な視点だと考えています。これもまた、Boring Engineering です。