3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この素晴らしいプロジェクトに爆焔を!〜負債だらけのレガシーコードを1万行消し飛ばした話〜

3
Posted at

光に覆われし漆黒よ。夜を纏いし爆炎よ。
紅魔の名のもとに原初の崩壊を顕現す。
終焉の王国の地に、力の根源を隠匿せし者。
我の前に統べよ!
エクスプロージョン(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行もコード書けない……」 とデスクに突っ伏し、動けなくなりました。

最後に

レガシーコードと戦うすべてのエンジニアに告ぎます。

時には、既存のコードを尊重し、少しずつ改善していく忍耐が必要です。
しかし、どうにもならない呪いに直面したときは、勇気を持って"破壊"を選択することも大切です。
すべてを灰燼に帰したあとにしか、作れない美しくクリーンな設計があるからです。

ただし、用法・用量を守らないと、プロジェクトごと吹き飛んで自分がクビになる可能性があるので、爆裂魔法の使用は計画的に。

それでは皆さん、良きエンジニアライフを!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?