アーキテクチャの進化戦略|漸進的改善とフルリライトの分岐点

アーキテクチャの進化戦略|漸進的改善とフルリライトの分岐点

「このシステム、いっそ一から作り直しませんか」

長く運用してきたコードの前では、チームの誰かが一度は口にする提案でしょう。

作り直せば理想の構造を描ける反面、何年も価値を届けられなくなる危うさも抱えます。

テックリードに求められるのは、賛成か反対かの気分ではなく、どこが分岐点なのかを言葉にすることです。

目の前のシステムは、作り直すべき状態と少しずつ直せる状態の、どちらに近いでしょうか。

全面書き直しが「最悪の判断」と呼ばれた理由

書き直しの危うさを語るときによく引かれるのが、Joel Spolsky が2000年に書いた記事です。

Netscape がコードを一から書き直すと決めたことを、彼はソフトウェア企業が犯しうる最悪の戦略ミスと呼びました。

Things You Should Never Do, Part I

Things You Should Never Do, Part I

記事の時点で、最後のメジャーリリース4.0から3年近くが経っていました。

彼が挙げた理由の核心は、古いコードに何年分ものバグ修正という知識が詰まっている点にあります。

読みにくく見える分岐も、実際の利用で見つかった問題への手当てかもしれない、という指摘です。

コードを捨てれば、その知識もまとめて捨てることになるのです。

分岐点は「問題の種類」と「前提の変化」

とはいえ、作り直しがいつも誤りというわけではありません。

Martin Fowler は2014年の記事「Sacrificial Architecture」で、数年後に捨てることを受け入れて作るという考え方を紹介しています。

例に挙がっているのは、Perl で始まり、成長に合わせて C++、Java へと作り直してきた eBay です。

Google については、10倍の成長を見込んで設計し、100倍になる前に書き直しを計画するという Jeff Dean の言葉が引かれていました。

一方の Joel も、構造の問題はコードを少しずつ移し、リファクタリングし、インターフェースを変えることで一つずつ解決できると書いています。

二人の主張を踏まえると、分かれ目は3つの問いに集約できそうです。

  • 問題の種類: 読みにくさや設計の歪みか、規模や要件といった前提の変化か
  • 途中の価値: 部分ごとに新しい仕組みへ切り替え、そのたびに成果を出せるか
  • 既存の挙動: 今のシステムが何をしているかを、仕様として説明し切れるか

前提が変わっていないなら、まず漸進的な改善を選ぶほうが後戻りしやすいでしょう。

漸進的な置き換えの代表例が、旧システムの周りで新しい部品を育てて役割を移していくストラングラーフィグです。

少しずつ直すときの合意の取り方は、こちらの記事で整理しました。

リファクタリングの合意形成|「動くコードを触るな」への答え方

進化し続けられる構造をフィットネス関数で守る

作り直しに追い込まれないためには、構造の崩れに早く気づく仕組みが欠かせません。

Neal Ford らの『Building Evolutionary Architectures』は、進化的アーキテクチャを「複数の観点にまたがる、導かれた漸進的な変化を支えるもの」と定義しました。

その「導く」役を担うのがフィットネス関数(fitness function)です。

Thoughtworks の Technology Radar によれば、アーキテクチャ特性の健全さを客観的に評価する仕組みで、単体テストやメトリクス、監視もその一部になりえます。

言い換えるなら、守りたい性質をテストや監視として自動で確かめるということです。

例えば Java なら、ArchUnit でパッケージ間の循環依存を禁止できます。

ArchUnit User Guide

NO IMAGE

概念を示すと、以下のようなイメージです。

// import 文は省略。パッケージ名は例
@AnalyzeClasses(packages = "com.myapp")
public class ArchitectureTest {

    @ArchTest
    public static final ArchRule noCycles =
        slices().matching("com.myapp.(*)..").should().beFreeOfCycles();

    // ... 他のルールは省略
}

CI で毎回走らせておけば、気づかないうちに依存が絡まっていく流れを、変更が入った時点で止められます。

分岐点で陥りやすい落とし穴

判断の場面での落とし穴も、3つ挙げておきます。

一つめは、今と同じ機能をそっくり作り直すフィーチャーパリティ(Feature Parity)を前提にした計画です。

Ian Cartwright らは記事「Feature Parity」で、その労力が大きく過小評価されやすいとして、一般には勧めていません。

旧システムには過去の不具合への回避策が要件として埋め込まれ、業務手順まで制約に合わせて形作られているためです。

二つめは、捨てる前提だからと中身を雑に作ることです。

Fowler は前述の記事でも、良いモジュール性は健全なコードベースに欠かせないと念を押しています。

境界がはっきりしていれば、全体を捨てる前に部品単位で入れ替える道も残せるのです。

三つめは、判断の前提を記録しないことです。

想定した成長の倍率や作り直しを見送った理由が残っていれば、前提が崩れたときに判断をやり直せます。

判断の残し方は、ADR の記事でまとめたとおりです。

ADRとは?技術判断の理由を残す記録の書き方

最後に

全面書き直しか漸進的改善かの分かれ目は、直したい問題が構造の中にあるのか、前提そのものが変わったのかにありました。

前提が変わっていないなら少しずつ直し、変わったなら作り直しを計画します。

どちらを選ぶにしても、守りたい性質を自動で確かめておけば、次の分岐点にも落ち着いて向き合えるでしょう。

私もテックリードを目指して学んでいる側なので、まずは守りたい性質を一つ書き出すところから始めようと考えています。

以上です。