光に覆われし漆黒よ。夜を纏いし爆炎よ。
紅魔の名のもとに原初の崩壊を顕現す。
終焉の王国の地に、力の根源を隠匿せし者。
我の前に統べよ!
エクスプロージョン(rm -rf)!!
我が名はエンジニア!
クリーンアーキテクチャを極めし者、そしてレガシーコードの破壊を渇望する者!
皆さんは、「触ると爆発するプロジェクト」にアサインされたことはありますか?
私はあります。というか、つい先日までその魔境に幽閉されていました。
本記事は、肥大化しきったスパゲティコードと複雑怪奇な密結合に対して、部分的なリファクタリングを諦め、「すべてを消し飛ばしてクリーンな設計へ再構築する」という爆裂魔法を放った1人のエンジニアの記録です。
(※技術的な詳細よりも、ポエムとカタルシス成分が多めです。温かい目でお読みください。)
第1章:始まりは「絞り込み検索をつけるだけ」の簡単なクエスト
ある日、PMからこんな依頼が飛んできました。
PM「商品一覧画面に、カテゴリの絞り込み検索をつけてもらえる?」
私「承知しました(まあ既存のリストにフィルター処理を追加するだけだろう)」
軽い気持ちでプロジェクトを git clone し、該当の画面コンポーネントを探しました。
しかし、そこには目を疑うような光景が広がっていたのです。
// 3000行を超える伝説のファイル `ProductListAndOtherStuff.tsx`
const ProductList = () => {
// なぜかここでユーザーのログイン状態やカート情報まで管理している
const [userData, setUserData] = useState<any>(null);
const [cart, setCart] = useState<any[]>([]);
useEffect(() => {
// 画面のスクロール量に応じて謎のアニメーションを発火させつつ、
// グローバルなwindowオブジェクトに直接フラグを立てる
window.hasScrolled_DO_NOT_TOUCH = true;
// TODO: 誰かこの状態管理どうにかして。2021年の俺より。
// FIXME: たまに無限ループしてブラウザがフリーズする
}, [userData, cart]);
// 以下、2000行にわたるネストの深いJSXと三項演算子の地獄...
}
……お前は何を言っているんだ?
たかが一覧を表示するだけなのに、なぜカートの状態まで抱え込んでいるのか。
引数とStateの any の暴力。単一責任の原則を粉砕する「神(God)コンポーネント」。
そして燦然と輝く // TODO: 誰かこの状態管理どうにかして の文字。
私はそっとエディタを閉じ、虚無を見つめました。
第2章:飛来するバグ
嫌な予感は的中しました。
このプロジェクトは、過去数年にわたり何人ものエンジニアが「その場しのぎの秘伝のタレ」を継ぎ足し続けた、アンチパターンの見本市だったのです。
- UIとビジネスロジックが完全に一体化した密結合
- 神(God)クラスと化した3000行のファイル
- ランタイムでしか気づけない
undefined is not a functionの呪い - 変更すると全く関係ないヘッダーが崩れる謎の依存関係
絞り込み検索を追加するためにStateを1つ足そうものなら、副作用の連鎖によって別のコンポーネントが予期せぬ再レンダリングを起こし、画面が真っ白になりました。
それはまるで、次から次へと押し寄せるキャベツの群れ。
1つのバグを切り伏せても、また別の場所から新しいバグが飛来してくるのです。
私は、このままでは自分が壊れてしまうと悟りました。
第3章:詠唱開始
通常、こうしたレガシーコードには「少しずつテストを書きながら、安全にリファクタリングしていく」のが定石です。ボーイスカウト・ルールです。
しかし、この魔境においては、テストを書くためのモックすら作れないほど結合度が密(ミツ)でした。何より、私は責務の曖昧な密結合がこの世で最も嫌いなのです。
私は決断しました。
「この神コンポーネント、根元からすべて消し飛ばして、適切な単位に切り出して作り直した方が早い」 と。
周囲の「えっ、ゼロから書き直すの? リスク高くない?」という不安の声を背に受けながら、私は最強の魔法を唱える準備に入りました。
黒より黒く闇より暗きスパゲティコードに、我がリファクタの混淆を望みたもう。
覚醒のとき来たれり。無謬の境界に落ちし仕様。密結合の歪みとなりて現出せよ!
踊れ踊れ踊れ、我が力の奔流に望むは再構築なり。並ぶ者なき再構築なり。
万象等しく灰塵に帰し、深淵より来たれ!
これがエンジニア最大の威力の解決手段、これこそが究極の破壊コマンド!
第4章:エクスプロージョン!
複雑怪奇な状態管理、無駄な副作用、ネストの深すぎるJSX。
それらをすべて選択し、私はターミナルに破壊の呪文を打ち込みました。
# さようなら、すべての負債たち
$ git rm -rf src/components/GodList/
$ git commit -m "feat: 諸悪の根源を爆砕"
消し飛んだコード:約10,000行。
そこからは無我の境地です。
ビジネスロジックと状態管理はカスタムフックへとカプセル化。
UIは見た目だけを担当するプレゼンテーショナルコンポーネントへ分割。
Strict に設定された TypeScript による堅牢な型定義の付与。
複雑な条件分岐はシンプルに整理され、謎のグローバル変数は完全に排除されました。
// 爆焔のあとに残された、美しき世界
export const ProductList: React.FC = () => {
// 複雑な状態管理と副作用はすべてフックの奥底へ封印
const { products, isLoading, error } = useFilteredProducts();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div className="grid">
{products.map(product => (
<ProductCard key={product.id} data={product} />
))}
</div>
);
};
UIとロジックの完全な分離。
圧倒的な可読性とテストのしやすさ。これが原初の光です。
終章:爆焔のあとに
数日後。
無事にPull Requestがマージされ、本番環境へデプロイされました。
懸念されていたバグは嘘のように消え去り、絞り込み検索は軽快に動作していました。
PM「おお、完璧! 動作もなんだか早くなったね!」
私「……(無言のサムズアップ)」
コードベースは劇的に軽量化され、後から入ってくるメンバーも安心して触れる、見通しの良いプロジェクトへと生まれ変わりました。
しかし、代償は小さくありませんでした。
魔力(体力と精神力)を限界まで使い果たした私は、その日の午後、「もう……今日は1行もコード書けない……」 とデスクに突っ伏し、動けなくなりました。
最後に
レガシーコードと戦うすべてのエンジニアに告ぎます。
時には、既存のコードを尊重し、少しずつ改善していく忍耐が必要です。
しかし、どうにもならない呪いに直面したときは、勇気を持って"破壊"を選択することも大切です。
すべてを灰燼に帰したあとにしか、作れない美しくクリーンな設計があるからです。
ただし、用法・用量を守らないと、プロジェクトごと吹き飛んで自分がクビになる可能性があるので、爆裂魔法の使用は計画的に。
それでは皆さん、良きエンジニアライフを!