Skip to content
Anonymous

重構的藝術:從 Copy-Paste 到 Layout 模式

探討如何運用 DRY 原則與 Layout 組件優化 Astro 專案架構。以 ToolLayout 遷移為例,說明抽象化重複 UI 模式的實務技巧與效益。

#refactoring #astro #clean-code #architecture #dry

在軟體工程中,有一句名言:「不要重複你自己 (Don’t Repeat Yourself, DRY)」。然而,在開發網頁應用時,我們經常會因為「方便」而陷入複製貼上的陷阱,尤其是在建立多個結構相似的頁面時。

本文將以本專案最近進行的「工具頁面重構」為例,探討如何識別重複模式,並利用 Layout Pattern (佈局模式) 進行優化,從而建立更易於維護的程式碼架構。

問題:複製貼上的代價

在這個專案中,我們有 16 個不同的線上工具(如 Base64 轉換器、UUID 產生器、QR Code 產生器等)。每個工具頁面都有一個完全相同的標頭區塊,包含:

  1. 「返回工具列表」的連結
  2. 工具的大標題
  3. 工具的副標題
  4. 代表該工具的 Emoji 圖示

在重構之前,每個頁面的程式碼長這樣:

<!-- src/pages/tools/base64.astro -->
<BaseLayout title="Base64 Converter | Tools" ...>
  <section class="max-w-4xl mx-auto px-4 ...">
    <!-- 重複的標頭區塊 -->
    <header class="mb-8">
      <div class="flex items-center gap-4 mb-4">
        <a href="/tools">← Back to Tools</a>
      </div>
      <div class="flex items-center gap-4">
        <span class="text-4xl">🔄</span>
        <div>
          <h1 class="text-3xl font-bold">Base64 Converter</h1>
          <p class="text-hacker-muted">Encode and decode Base64 strings</p>
        </div>
      </div>
    </header>
    
    <!-- 工具的主要內容 -->
    <div class="tool-content">...</div>
  </section>
</BaseLayout>

這樣的結構看似沒問題,但隱藏了巨大的維護成本:

  • 修改困難:如果今天我想把「返回連結」的樣式改掉,我必須手動修改 16 個檔案。
  • 容易出錯:在複製貼上的過程中,可能會不小心遺漏某些 class 或結構,導致不同頁面的外觀不一致。
  • 程式碼冗餘:每個檔案都充斥著相同的 HTML 結構,模糊了該頁面真正的「業務邏輯」(即工具本身的功能)。

解決方案:Layout Isolation (佈局隔離)

為了解決這個問題,我們採取了 「提取 Layout」 的策略。

什麼是 Layout Pattern?

在 Astro(以及 React、Vue 等現代框架)中,Layout 不僅僅是用來包裹整個頁面的最外層容器(如 BaseLayout),我們也可以為「特定一組頁面」建立專屬的 Layout。

這個技術的核心概念是 抽象化 (Abstraction):將「不變的部分」提取出來,將「會變的部分」作為參數 (Props) 或插槽 (Slot) 傳入。

實作 ToolLayout

我們建立了一個新的 ToolLayout.astro 組件,它負責處理所有工具頁面共有的 UI 邏輯:

---
// src/layouts/ToolLayout.astro
import BaseLayout from './BaseLayout.astro';

interface Props {
  title: string;
  description: string;
  toolTitle: string;    // i18n key
  toolSubtitle: string; // i18n key
  emoji: string;
  class?: string;
}

const { title, description, toolTitle, toolSubtitle, emoji, class: className } = Astro.props;
---

<BaseLayout title={title} description={description}>
  <section class={`max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 ${className}`}>
    <!-- 統一管理的 Header -->
    <header class="mb-8">
      <div class="flex items-center gap-4 mb-4">
        <a href="/tools" class="..." data-i18n="toolsCommon.backToTools">← Back to Tools</a>
      </div>
      <div class="flex items-center gap-4">
        <span class="text-4xl">{emoji}</span>
        <div>
          <h1 class="text-3xl font-bold text-hacker-text" data-i18n={toolTitle}></h1>
          <p class="text-hacker-muted" data-i18n={toolSubtitle}></p>
        </div>
      </div>
    </header>

    <!-- 工具的獨特內容會插入在這裡 -->
    <slot />
  </section>
</BaseLayout>

重構後的頁面

現在,原本的 base64.astro 變得非常乾淨,只專注於它該做的事情——實現 Base64 轉換邏輯:

---
import ToolLayout from '../../layouts/ToolLayout.astro';
---

<ToolLayout 
  title="Base64 Converter | Tools" 
  description="Encode and decode Base64 strings."
  toolTitle="tools.base64.title"
  toolSubtitle="tools.base64.subtitle"
  emoji="🔄"
>
  <!-- 這裡只需要寫工具本身的 UI -->
  <div class="glass rounded-xl p-4">
    <textarea id="input" ...></textarea>
  </div>
</ToolLayout>

技術效益總結

透過這次重構,我們獲得了以下好處:

  1. 單一事實來源 (Single Source of Truth):Header 的結構與樣式只定義在 ToolLayout.astro 一個地方。若需修改樣式,只需修改一個檔案,所有 16 個工具頁面會同步更新。
  2. 關注點分離 (Separation of Concerns):工具頁面不再需要關心「標題長什麼樣子」或「返回按鈕放哪裡」,開發者可以專注於工具的功能實作。
  3. 提升可讀性 (Readability):移除冗餘代碼後,檔案行數減少,邏輯更清晰。
  4. 強制一致性 (Consistency):所有使用 ToolLayout 的頁面,其標題排版、間距絕對保證一致,杜絕了人工複製貼上造成的細微差異。

結語

重構並不是為了寫出「完美的程式碼」,而是為了寫出「可維護的程式碼」。

當你在專案中發現自己複製貼上了同一段程式碼超過三次時,請停下來思考一下:這是不是一個提取 Layout 或 Component 的好機會?雖然當下可能需要花幾十分鐘進行重構,但這筆投資在未來的維護工作中,將會以十倍、百倍的效率回報給你。