Skip to content

程式碼品質與重構

前言

程式碼寫出來能跑就行了嗎? 你可能寫過這樣的程式碼:功能是實現了,但過了兩週自己都看不懂了。或者團隊裡有人離職,留下一堆「只有上帝和他才能看懂」的程式碼。

本章帶你理解什麼是好程式碼,如何識別壞程式碼,以及如何安全地改進它。

這篇文章會帶你學什麼?

章節內容核心概念
第 1 章程式碼壞味道識別常見問題
第 2 章重構手法安全地改進程式碼
第 3 章程式碼審查團隊協作中的品質保障
第 4 章品質度量用資料衡量程式碼健康度

學完本章,你將掌握識別程式碼問題、安全重構、以及透過團隊協作持續提升程式碼品質的方法。


0. 全景圖:程式碼的生命週期

在軟體開發中,有一個常被忽視的事實:程式碼被閱讀的次數遠遠多於被編寫的次數

一段程式碼從誕生到退役,大致會經歷這樣的旅程:

程式碼的一生

  • 編寫階段:開發者寫下第一版實現,功能跑通了,測試通過了。
  • 審查階段:團隊成員閱讀程式碼,提出改進建議。
  • 維護階段:修 Bug、加功能、適應新需求——這個階段佔據了程式碼生命週期的 80% 以上。
  • 重構階段:當程式碼變得難以維護時,需要在不改變外部行為的前提下改善內部結構。
  • 退役階段:技術迭代,舊程式碼被新方案替代。

Martin Fowler 在《重構》一書中說過:「任何一個傻瓜都能寫出電腦能理解的程式碼,唯有好的程式設計師才能寫出人類能理解的程式碼。」


1. 程式碼壞味道:識別常見問題

1.1 什麼是程式碼壞味道?

「程式碼壞味道」(Code Smell)這個概念由 Kent Beck 提出,指的是程式碼中那些雖然不是 Bug,但暗示著更深層設計問題的特徵。就像房間裡有股怪味——不會立刻讓你生病,但說明某個地方需要清理了。

透過下面的互動元件,識別幾種最常見的程式碼壞味道:

Code Smell Detector - click to switch examples
Problem code
function processOrder(order) {
  // Validate order... (20 lines)
  // Calculate price... (15 lines)
  // Check inventory... (10 lines)
  // Send notification... (15 lines)
  // Update database... (10 lines)
  // Generate report... (10 lines)
  // 80+ lines in total!
}

📏 Long function

A function exceeds 50 lines, does too many things, and becomes hard to understand and test.

Improvement:Split the large function into focused functions such as validateOrder(), calculatePrice(), and checkInventory().

1.2 常見壞味道清單

壞味道症狀危害
過長函式函式超過 50 行難以理解、測試和複用
魔法數字程式碼中直接寫 86400000含義不明,修改時容易遺漏
重複程式碼相似邏輯出現在很多地方修改時必須同步多處,容易遺漏
過深巢狀超過 3 層的 if/for邏輯像迷宮,難以追蹤
過長參數列表函式參數超過 4 個呼叫困難,容易傳錯順序
上帝類別一個類別/模組做了太多事職責不清,牽一髮動全身

核心洞察

壞味道不是「錯誤」,而是「訊號」。它告訴你:這裡的設計可能需要改進。不是所有壞味道都需要立刻修復,但你需要有能力識別它們。


2. 重構手法:安全地改進程式碼

2.1 什麼是重構?

重構(Refactoring)的定義非常精確:在不改變程式碼外部行為的前提下,改善其內部結構。

關鍵詞是「不改變外部行為」。重構不是重寫,不是加功能,不是修 Bug。它是對程式碼內部的「整理收納」。

透過下面的元件,對比幾種常見重構手法的前後變化:

Refactoring technique comparison - choose a technique to compare before and after
Extract Function: move a code block out of a large function into a clearly named new function.
Before
function printReport(invoice) {
  console.log("=== Invoice ===")
  // Calculate total
  let total = 0
  for (let item of invoice.items) {
    total += item.price * item.qty
  }
  console.log(`Total: ${total}`)
}
After
function printReport(invoice) {
  console.log("=== Invoice ===")
  const total = calcTotal(invoice.items)
  console.log(`Total: ${total}`)
}

function calcTotal(items) {
  return items.reduce(
    (s, i) => s + i.price * i.qty, 0
  )
}
Key point:Extract Function is one of the most common refactorings. A good function name is the best comment.

2.2 常用重構手法

提煉函式(Extract Function)

這是最常用的重構手法。當一段程式碼可以用一個有意義的名字來概括時,就應該把它提煉成函式。

javascript
// 重構前
function printReport(data) {
  // 計算總價
  let total = 0
  for (const item of data.items) {
    total += item.price * item.qty
  }
  // 列印...
}

// 重構後
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0)
}

function printReport(data) {
  const total = calculateTotal(data.items)
  // 列印...
}

重新命名(Rename)

好的命名是最廉價也最有效的文件。當你需要寫註解來解釋一個變數/函式的含義時,說明它的名字不夠好。

javascript
// 重構前
const d = new Date() - startTime  // 經過的時間
const arr = users.filter(u => u.a) // 活躍使用者

// 重構後
const elapsedMs = new Date() - startTime
const activeUsers = users.filter(user => user.isActive)

用衛語句替代巢狀(Replace Nested Conditional with Guard Clauses)

javascript
// 重構前
function getPayAmount(employee) {
  if (employee.isSeparated) {
    return { amount: 0 }
  } else {
    if (employee.isRetired) {
      return { amount: employee.pension }
    } else {
      return { amount: employee.salary }
    }
  }
}

// 重構後
function getPayAmount(employee) {
  if (employee.isSeparated) return { amount: 0 }
  if (employee.isRetired) return { amount: employee.pension }
  return { amount: employee.salary }
}

重構的安全網

重構最大的風險是「改著改著就改出 Bug 了」。所以重構的前提是有測試覆蓋。每次小步重構後執行測試,確保行為沒變。沒有測試的程式碼,先補測試再重構。


3. 程式碼審查:團隊協作中的品質保障

3.1 為什麼需要程式碼審查?

程式碼審查(Code Review)是團隊中最有效的品質保障手段之一。它的價值不僅在於發現 Bug,更在於:

  • 知識共享:團隊成員了解彼此的程式碼,降低「巴士因子」(如果某人被公車撞了,專案還能繼續嗎?)
  • 統一風格:透過審查逐步形成團隊的編碼規範
  • 提前發現設計問題:比 Bug 更難修的是糟糕的架構決策
  • 互相學習:看別人的程式碼是提升程式設計能力的捷徑

3.2 審查什麼?

維度關注點
正確性邏輯是否正確?邊界條件是否處理?
可讀性命名是否清晰?結構是否易懂?
安全性是否有注入風險?敏感資料是否暴露?
效能是否有明顯的效能問題?N+1 查詢?
測試是否有對應的測試?覆蓋了關鍵路徑嗎?

3.3 審查的禮儀

好的程式碼審查是對程式碼的討論,而不是對人的批評

  • 用「我們」而不是「你」:「你這裡寫錯了」 → 「這裡我們可以考慮用 guard clause」
  • 提問而不是命令:「改成 const」 → 「這個變數後面會被重新賦值嗎?如果不會,用 const 更安全」
  • 給出理由:不只說「不好」,要說「為什麼不好」以及「怎樣更好」

4. 程式碼品質度量

4.1 圈複雜度

圈複雜度(Cyclomatic Complexity)衡量程式碼中獨立路徑的數量。每個 ifforcase&&|| 都會增加複雜度。

複雜度評價建議
1-10簡單容易理解和測試
11-20中等考慮拆分
21-50複雜必須重構
50+不可維護緊急重構

4.2 程式碼覆蓋率

程式碼覆蓋率衡量測試執行了多少比例的程式碼。常見指標:

  • 行覆蓋率:被執行的程式碼行佔總行數的比例
  • 分支覆蓋率:被執行的條件分支佔總分支的比例

覆蓋率的陷阱

80% 的覆蓋率不代表程式碼品質好。覆蓋率只能告訴你「哪些程式碼沒被測試到」,不能告訴你「測試是否有意義」。一個只斷言 expect(true).toBe(true) 的測試可以提高覆蓋率,但毫無價值。

4.3 實用工具

工具用途
ESLintJavaScript/TypeScript 靜態分析
Prettier程式碼格式化,統一風格
SonarQube綜合程式碼品質平台
HuskyGit hooks,提交前自動檢查

5. AI 助力:用大模型提升程式碼品質

大模型在程式碼品質領域已經非常實用,它可以充當你的「24 小時線上的程式碼審查員」。

5.1 識別程式碼壞味道

提示詞

請審查以下程式碼,識別其中的程式碼壞味道(Code Smell),包括但不限於:
過長函式、魔法數字、重複程式碼、過深巢狀、過長參數列表。
對每個問題給出具體位置、問題描述和改進建議。

[貼上你的程式碼]

5.2 自動重構

提示詞

請對以下程式碼進行重構,要求:
1. 不改變外部行為
2. 使用提煉函式、衛語句替代巢狀等手法
3. 改善命名,消除魔法數字
4. 解釋每一步重構的理由

[貼上你的程式碼]

5.3 模擬 Code Review

提示詞

請以資深開發者的視角審查這段程式碼,從以下維度給出回饋:
- 正確性:邏輯是否有 Bug?邊界條件是否處理?
- 可讀性:命名是否清晰?結構是否易懂?
- 效能:是否有明顯的效能問題?
- 安全性:是否有注入或資料洩露風險?
用「建議」而非「命令」的語氣,給出改進方案。

[貼上你的程式碼]

AI 使用建議

AI 的重構建議需要你自己驗證——跑測試確認行為沒變。把 AI 當作「提建議的同事」,而不是「無條件信任的權威」。


6. 總結

回顧這一路,我們從識別問題到解決問題,建立了一套完整的程式碼品質改進體系:

  1. 識別:學會聞到程式碼壞味道,知道哪裡需要改進
  2. 重構:掌握安全的重構手法,在測試保護下小步改進
  3. 協作:透過程式碼審查,讓團隊共同守護程式碼品質
  4. 度量:用客觀指標追蹤程式碼健康度

終極思考

程式碼品質不是一次性的工作,而是持續的習慣。就像保持房間整潔一樣——不是等到亂得不行了才大掃除,而是每天隨手整理。童子軍法則說得好:離開時讓程式碼比你來時更乾淨一點。


延伸閱讀

  • 經典書籍:Martin Fowler《重構:改善既有程式碼的設計》是這個領域的聖經。
  • 程式碼整潔之道:Robert C. Martin《Clean Code》提供了大量實用的編碼原則。
  • 實用工具:嘗試在專案中設定 ESLint + Prettier + Husky,體驗自動化程式碼品質保障。
  • 程式碼審查:Google 的 Code Review 指南是業界標竿,值得學習。