「請求書の表示を直しただけなのに、なぜか見積書の画面が壊れました」——受託でシステムを引き継ぐと、こうした「一か所直すと、関係ないはずの別の場所が壊れる」事故にしばしば出会います。調べていくと、原因は決まって同じ構図にたどり着きます。かつて「似ているから」と一つにまとめた共通の関数やコンポーネントが、いつの間にか別々の都合を一身に背負い、どこを直しても全員に波及するようになっている。良かれと思って行った共通化が、システム全体を「触ると壊れる」状態にしているのです。
この問題を最も的確に言い当てているのが、Sandi Metz の有名な記事 The Wrong Abstraction(sandimetz.com) です。要点は「重複は、間違った抽象化よりはるかに安い(duplication is far cheaper than the wrong abstraction)」。早すぎる段階で無理に共通化するくらいなら、しばらく重複させておくほうが、後の保守はずっと楽になる——という洞察です。本記事では、受託で引き継いだ「共通化しすぎたコード」をどう見極め、どう安全に解きほぐすかを、システム開発の立場から整理します。
なぜ「早すぎる共通化」が事故を生むのか
コードの共通化(抽象化)そのものは、本来は良いことです。問題は、まだ「本当に同じ」と言い切れない段階で、見た目が似ているだけのコードを一つにまとめてしまうことです。
最初は、請求書と見積書の表示処理がそっくりだったとします。「同じだから」と一つの関数にまとめる。ところがその後、請求書だけに税の特例が入り、見積書だけに有効期限が付く。共通関数は、その都度「請求書のときは」「見積書のときは」という分岐(フラグ)を抱え込んでいきます。気づけば、一つの関数が両方の都合を背負い、if の枝が増え、どちらを直しても、もう一方に影響が出る。「一つにまとめた」はずが、「二つの異なる処理が一か所で絡み合った」状態になっているのです。
| 観点 | 適切な重複 | 間違った抽象化 |
|---|---|---|
| 変更の影響範囲 | その箇所だけ | 共有する全箇所に波及 |
| コードの見た目 | 似た処理が複数 | 一つにまとまり一見きれい |
| 分岐(フラグ) | 少ない | 用途ごとに増え続ける |
| 引き継ぎやすさ | 局所的で読める | 全体を理解しないと触れない |
厄介なのは、間違った抽象化は一見きれいに見えることです。重複が消え、コードがまとまっているので、「よく設計されている」と誤解されやすい。しかし実態は、別々であるべきものが癒着している。なぜその構造にしたのかという経緯が残っていないと、後任は「触ると何が壊れるか分からない」まま引き継ぐことになります。設計判断の経緯を残す手法は、ADR(設計決定記録)の記事で扱ったとおりで、これがないと癒着の解きほぐしはさらに難しくなります。
「間違った抽象化」を見分けるサイン
引き継いだコードが間違った抽象化に陥っているかは、次のサインで見分けられます。
第一に、用途を切り替えるフラグや引数が増え続けていること。type や isInvoice のような分岐が関数の頭にいくつも並び、呼び出し側がそれを指定している。これは「一つの抽象が複数の異なる用途を兼ねている」典型的な兆候です。
第二に、共通部品を直すと、関係ないはずの画面のテストが落ちること。影響範囲が予測できないなら、それは「別々であるべきもの」が一つになっている証拠です。
第三に、「この分岐は何のためか」を誰も説明できないこと。当初の「同じ」という前提が崩れたあとも、惰性で共通化が維持されている状態です。
これらが揃っていたら、無理にもっと賢い共通化を重ねるより、いったん重複に戻すほうが安全なことが多い。Sandi Metz の言う「間違った抽象化のもとへ戻るより、重複を受け入れて前へ進む」とは、まさにこの判断です。
安全に「重複へ戻す」進め方
引き継いだシステムでこれを解きほぐすときの手順は、おおむね次のとおりです。
まず、癒着した共通部品を、用途ごとにコピーして分離する。請求書用と見積書用に、いったん同じcoードを二つに複製し、それぞれの呼び出し元を新しいコピーに向け替えます。この時点ではコードは増えますが、お互いの影響が切れるのが重要です。
次に、分離したそれぞれから、不要な分岐を削る。請求書用のコピーからは見積書のための if を消し、見積書用からは請求書のための分岐を消す。すると、それぞれが「その用途だけの素直なコード」になり、ぐっと読みやすくなります。
最後に、本当に共通な部分だけを、改めて見極めて括り出す。すべてを一つにするのではなく、「明らかに同じで、今後も分かれない」とわかった部分だけを共有に戻す。この順序を守ると、外部APIを呼ぶ処理を堅牢な土台に寄せる発想——fetch API で壊れにくいHTTPクライアントを作る記事で扱ったような「本当に共通な土台」だけを共有する形——に自然と落ち着きます。
受託で組み込むときの落とし穴
弊社が改修を引き継いだある業務システム(社名は伏せます)では、帳票を出力する共通コンポーネントが一つあり、請求書・見積書・納品書・領収書の四種類すべてがそれを使っていました。中身は分岐だらけで、領収書の文言を一つ直すよう依頼されただけなのに、請求書のレイアウトが崩れる、という事故が実際に起きていました。前任者は「共通化されているから効率的」と説明していましたが、実態は四つの帳票が一か所で絡み合い、誰も全体を把握できずにいたのです。
私たちは、この共通コンポーネントを四つにコピーして分離し、それぞれから他の帳票のための分岐を削りました。コード量は一時的に増えましたが、領収書を直しても請求書には一切波及しなくなり、各帳票は「その帳票だけの素直なコード」になりました。そのうえで、本当に共通だったヘッダーのロゴ描画など、ごく一部だけを共有に戻しました。やったのは、賢い共通化を足すことではなく、間違った癒着を一度ほどいてから、本当に同じ部分だけを括り直すことです。
この案件で一番効いた学びは、「重複は悪、共通化は善」と単純に考えないことでした。コードの行数だけ見れば重複は無駄に見えますが、保守のコストで見れば、無理な共通化のほうがずっと高くつく。リポジトリや依存を一か所に集める判断と同じく、何を共有し何を分けるかは「保守のしやすさ」で決めるべきで、モノレポで依存を整理する記事で扱った「集約と分離の線引き」と同じ判断軸が、コードの抽象化にもそのまま当てはまります。
どこから着手するか
「共通化されているから良いコード」という思い込みを外し、「変更の影響範囲が読めるか」で良し悪しを判断するところから、引き継いだシステムは安全になります。フラグが増え続け、一か所直すと別の場所が壊れる共通部品があれば、それは賢く直す対象ではなく、いったん重複に戻す対象かもしれません。
最初の一歩としては、いまのシステムで「用途を切り替えるフラグが多い共通部品」を一つ探し、それを使っている画面の数と、最近そこで起きた予期しない不具合を洗い出すことをお勧めします。影響範囲が読めない部品が見つかれば、それが解きほぐしの優先候補です。
引き継いだシステムで一か所直すと別の場所が壊れる、共通化されているはずなのに改修のたびに事故が起きる、どこまで共有してどこを分けるべきか判断したい——そうしたお悩みがあれば、グリームハブのお問い合わせからご相談ください。現行コードの抽象化の状態を拝見し、間違った癒着を安全にほどいて、影響範囲の読める構造へ段階的に作り直す設計をご一緒に組みます。