社内で開発しているアプリケーションが「大きな泥団子」状態になってしまっているため、開発効率が上がらない問題に直面した。そこでリファクタリングを決意。
「クリーンアーキテクチャ」、「マイクロサービスアーキテクチャ」「リファクタリング」などの書籍で勉強をしつつ、リファクタリングを行っている。
まだ完了した訳ではないけれど、リファクタリングを行うにあたり考えてきたことを整理する。
モノシリックアプリケーションの現状整理
リファクタリングを行う前のアプリケーションの状態を整理してみた。
以下の問題を抱えている。
- 複数のサブシステム、コンポーネントを一つのgitリポジトリで一括管理(ソースコード量は70万行くらい)
- これがメインの問題。この構成になっていることによって機能開発が一部のサブシステムに閉じていたとしても、CIで全てのモジュールをビルド・テストする羽目になる。
- 一つのDBを全てのサブシステムが共有している
- DBのテーブルを少し変更するだけで全てのサブシステムに影響が及ぶ可能性がある。
- どのテーブルを変更すれば、機能開発で実現したいことができるのか調査に時間がかかる。
- 自動テストが検証したい機能以外と密結合すぎる
- テストコードが下位モジュールの詳細に依存している。結合テストのような位置付けでテストが実装されているため、下位モジュールのプロダクトコードを修正すると、色んなテストがこける。
- DBのデータをロードして行うテストもあり、メンテコストが高い。
- 一度のCIでビルドとテストを行うと2時間くらいかかる
- gitリポジトリが一つであることと、単体テストコードが結合テストのような位置付けで実装されていることが主な原因。自動テストによる品質担保のメリットより、開発アジリティ阻害のデメリットの方が大きい。
- 下位モジュールに詳細が含まれすぎている
- 下位のモジュールに詳細が含まれていることによって、機能開発では下位モジュールを頻繁に更新することになる。また、上位モジュールは詳細が下位モジュールに存在することによって、その詳細に依存することになる。結果として修正に閉じることができなくなっている。
問題へのリファクタリング
-
複数のサブシステム、コンポーネントを一つのgitリポジトリで一括管理
- 現在は「機能単位」で分割した複数のmavenプロジェクトを一つのgitリポジトリに収めているが、それを単一責任の原則に従って切り分け、別リポジトリに分割する。
-
一つのDBを全てのサブシステムが共有している
- DBを共有していることによって、一つのテーブルには複数の関心が詰め込まれている。よって、必然的にテーブルのカラムは膨大な量になっている。これを各サブシステム毎に必要なカラムのみを抽出したテーブルに分割し、スキーマをサブシステム単位で分割する。
-
自動テストが検証したい機能以外と密結合すぎる
- 既存テストコードから外部仕様を把握し、下位モジュールの抽象に依存した形でテストコードを書きながら、既存実装をリファクタリングしていく。
-
一度のCIでビルドとテストを行うと2時間くらいかかる
- gitリポジトリ一括管理、単体テストコードと実装の密結合をリファクタリングをすることで解消される
-
下位モジュールに詳細が含まれすぎている
- 依存性逆転の原則を適用するようにリファクタリング。下位のモジュールは基本的には抽象クラス、インターフェースのみで構成されることを目指す。
リファクタリングの効果試算
-
複数のサブシステム、コンポーネントを一つのgitリポジトリで一括管理
- ソースコード変更に対するビルド・テスト範囲が約半分になると仮定すると、ビルド・テスト時間も単純に半分になると仮定。既存のビルド・テスト時間を2時間と仮定し、一日の平均CI回数を5回と仮定すると、
5(ビルド回数)×2×0.5(リファクタリングにより削減された時間)×20(営業日) = 100時間/1ヶ月
のコスト削減が開発者一人あたりに対して見込める。
- ソースコード変更に対するビルド・テスト範囲が約半分になると仮定すると、ビルド・テスト時間も単純に半分になると仮定。既存のビルド・テスト時間を2時間と仮定し、一日の平均CI回数を5回と仮定すると、
-
一つのDBを全てのサブシステムが共有している
- DBの変更を伴う機能開発での無影響確認・DB変更箇所の調査にかかる時間を16時間(2人日)と仮定、これがDB分割を行った後では2時間になると仮定する。DB変更を伴う機能開発が月に3回発生すると仮定すると、
3(機能開発発生回数)×14(リファクタリングにより削減された時間) = 42時間/1ヶ月
のコスト削減が開発者一人あたりに対して見込める。
- DBの変更を伴う機能開発での無影響確認・DB変更箇所の調査にかかる時間を16時間(2人日)と仮定、これがDB分割を行った後では2時間になると仮定する。DB変更を伴う機能開発が月に3回発生すると仮定すると、
-
自動テストが検証したい機能以外と密結合すぎる
- 自動テストを修正するのにかかる時間を5時間と仮定、これがリファクタリングを行った後では1時間になると仮定する。自動テストを修正する機会が月に5回発生すると仮定すると、
5(機能開発発生回数)×4(リファクタリングにより削減された時間) = 20時間/1ヶ月
のコスト削減が開発者一人あたりに対して見込める。
- 自動テストを修正するのにかかる時間を5時間と仮定、これがリファクタリングを行った後では1時間になると仮定する。自動テストを修正する機会が月に5回発生すると仮定すると、
-
下位モジュールに詳細が含まれすぎている
- これは下位モジュールの更新頻度に影響してくる。下位モジュールが抽象でのみ構成されていれば、上位のモジュールを全てビルドするという事態を避けられる。下位モジュールの更新頻度が月に10回だと仮定し、リファクタリングを行った後では月に1回になると仮定する。ソースコードのビルド・テスト時間を2時間と仮定すると、
2(ビルド・テスト時間)×9(リファクタリングにより削減された更新頻度) = 18時間/1ヶ月
のコスト削減が開発者一人あたりに対して見込める。
- これは下位モジュールの更新頻度に影響してくる。下位モジュールが抽象でのみ構成されていれば、上位のモジュールを全てビルドするという事態を避けられる。下位モジュールの更新頻度が月に10回だと仮定し、リファクタリングを行った後では月に1回になると仮定する。ソースコードのビルド・テスト時間を2時間と仮定すると、
試算から考えられること
上で行った試算は何一つ正確な数値ではない。こんな試算はそもそも正確にできるはずがない。
ただ、このリファクタリングを行うことによるメリットを伝えて、リファクタリングを行う許可を上位者から受けるために考えておかなければならない。
そして一番重要なのは、上記で試算したリファクタリングにより削減できるコストを、リファクタリングを行わない限り永久に支払い続けることになる、そしてそのコストは増え続けていくということ。
リファクタリングにより削減できるコストは試算したが、リファクタリングにかかるコストを試算していないのはこれが理由。
リファクタリングにより削減できるコストはリファクタリングを行うことによってしか削減できない。ということはリファクタリングを行うコストが現実的でない値(100人月とか)でなければ是が非でも行うべきだと思う。
コストはこれからも増え続けていくのに、リファクタリングにかかるコストが2人月だったら行う、3人月だったら行わないといった議論はそもそもナンセンスに思う。
リファクタリングを行って感じたこと
-
アーキテクトとして必要な知識・経験を高密度に得られる。
- 「優れたアーキテクトは実装の決定方針を最大限に遅延させることができる」とクリーンアーキテクチャでは言及されている。それを実現するにあたって、必要な**「依存関係の制御」、「モジュール管理」、「結合度」**を自分で考えて適用していくがリファクタリングである。そして、その効果を自分で身を以て体感できる。
-
現場に求められるエンジニアへ成長できる
- ソフトウェアは変化し続ける。なぜならビジネス要求は変化し続けるから。結果的に0からアプリケーションを作る力を発揮する場面よりも、既存のアプリケーションを拡張していく場面の方が圧倒的に多い。そのため、すでに動いているアプリケーションを破壊せずに開発効率を向上させ、メンテコストを下げることができるエンジニアはどんな市場においても必ず必要とされる。そういうエンジニアに求められるスキルをリファクタリングを通して得られる。