程式碼品質與重構
前言
程式碼寫出來能跑就行了嗎? 你可能寫過這樣的程式碼:功能是實現了,但過了兩週自己都看不懂了。或者團隊裡有人離職,留下一堆「只有上帝和他才能看懂」的程式碼。
本章帶你理解什麼是好程式碼,如何識別壞程式碼,以及如何安全地改進它。
這篇文章會帶你學什麼?
| 章節 | 內容 | 核心概念 |
|---|---|---|
| 第 1 章 | 程式碼壞味道 | 識別常見問題 |
| 第 2 章 | 重構手法 | 安全地改進程式碼 |
| 第 3 章 | 程式碼審查 | 團隊協作中的品質保障 |
| 第 4 章 | 品質度量 | 用資料衡量程式碼健康度 |
學完本章,你將掌握識別程式碼問題、安全重構、以及透過團隊協作持續提升程式碼品質的方法。
0. 全景圖:程式碼的生命週期
在軟體開發中,有一個常被忽視的事實:程式碼被閱讀的次數遠遠多於被編寫的次數。
一段程式碼從誕生到退役,大致會經歷這樣的旅程:
程式碼的一生
- 編寫階段:開發者寫下第一版實現,功能跑通了,測試通過了。
- 審查階段:團隊成員閱讀程式碼,提出改進建議。
- 維護階段:修 Bug、加功能、適應新需求——這個階段佔據了程式碼生命週期的 80% 以上。
- 重構階段:當程式碼變得難以維護時,需要在不改變外部行為的前提下改善內部結構。
- 退役階段:技術迭代,舊程式碼被新方案替代。
Martin Fowler 在《重構》一書中說過:「任何一個傻瓜都能寫出電腦能理解的程式碼,唯有好的程式設計師才能寫出人類能理解的程式碼。」
1. 程式碼壞味道:識別常見問題
1.1 什麼是程式碼壞味道?
「程式碼壞味道」(Code Smell)這個概念由 Kent Beck 提出,指的是程式碼中那些雖然不是 Bug,但暗示著更深層設計問題的特徵。就像房間裡有股怪味——不會立刻讓你生病,但說明某個地方需要清理了。
透過下面的互動元件,識別幾種最常見的程式碼壞味道:
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.
1.2 常見壞味道清單
| 壞味道 | 症狀 | 危害 |
|---|---|---|
| 過長函式 | 函式超過 50 行 | 難以理解、測試和複用 |
| 魔法數字 | 程式碼中直接寫 86400000 | 含義不明,修改時容易遺漏 |
| 重複程式碼 | 相似邏輯出現在很多地方 | 修改時必須同步多處,容易遺漏 |
| 過深巢狀 | 超過 3 層的 if/for | 邏輯像迷宮,難以追蹤 |
| 過長參數列表 | 函式參數超過 4 個 | 呼叫困難,容易傳錯順序 |
| 上帝類別 | 一個類別/模組做了太多事 | 職責不清,牽一髮動全身 |
核心洞察
壞味道不是「錯誤」,而是「訊號」。它告訴你:這裡的設計可能需要改進。不是所有壞味道都需要立刻修復,但你需要有能力識別它們。
2. 重構手法:安全地改進程式碼
2.1 什麼是重構?
重構(Refactoring)的定義非常精確:在不改變程式碼外部行為的前提下,改善其內部結構。
關鍵詞是「不改變外部行為」。重構不是重寫,不是加功能,不是修 Bug。它是對程式碼內部的「整理收納」。
透過下面的元件,對比幾種常見重構手法的前後變化:
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}`) }
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 ) }
2.2 常用重構手法
提煉函式(Extract Function)
這是最常用的重構手法。當一段程式碼可以用一個有意義的名字來概括時,就應該把它提煉成函式。
// 重構前
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)
好的命名是最廉價也最有效的文件。當你需要寫註解來解釋一個變數/函式的含義時,說明它的名字不夠好。
// 重構前
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)
// 重構前
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)衡量程式碼中獨立路徑的數量。每個 if、for、case、&&、|| 都會增加複雜度。
| 複雜度 | 評價 | 建議 |
|---|---|---|
| 1-10 | 簡單 | 容易理解和測試 |
| 11-20 | 中等 | 考慮拆分 |
| 21-50 | 複雜 | 必須重構 |
| 50+ | 不可維護 | 緊急重構 |
4.2 程式碼覆蓋率
程式碼覆蓋率衡量測試執行了多少比例的程式碼。常見指標:
- 行覆蓋率:被執行的程式碼行佔總行數的比例
- 分支覆蓋率:被執行的條件分支佔總分支的比例
覆蓋率的陷阱
80% 的覆蓋率不代表程式碼品質好。覆蓋率只能告訴你「哪些程式碼沒被測試到」,不能告訴你「測試是否有意義」。一個只斷言 expect(true).toBe(true) 的測試可以提高覆蓋率,但毫無價值。
4.3 實用工具
| 工具 | 用途 |
|---|---|
| ESLint | JavaScript/TypeScript 靜態分析 |
| Prettier | 程式碼格式化,統一風格 |
| SonarQube | 綜合程式碼品質平台 |
| Husky | Git hooks,提交前自動檢查 |
5. AI 助力:用大模型提升程式碼品質
大模型在程式碼品質領域已經非常實用,它可以充當你的「24 小時線上的程式碼審查員」。
5.1 識別程式碼壞味道
提示詞:
請審查以下程式碼,識別其中的程式碼壞味道(Code Smell),包括但不限於: 過長函式、魔法數字、重複程式碼、過深巢狀、過長參數列表。 對每個問題給出具體位置、問題描述和改進建議。 [貼上你的程式碼]
5.2 自動重構
提示詞:
請對以下程式碼進行重構,要求: 1. 不改變外部行為 2. 使用提煉函式、衛語句替代巢狀等手法 3. 改善命名,消除魔法數字 4. 解釋每一步重構的理由 [貼上你的程式碼]
5.3 模擬 Code Review
提示詞:
請以資深開發者的視角審查這段程式碼,從以下維度給出回饋: - 正確性:邏輯是否有 Bug?邊界條件是否處理? - 可讀性:命名是否清晰?結構是否易懂? - 效能:是否有明顯的效能問題? - 安全性:是否有注入或資料洩露風險? 用「建議」而非「命令」的語氣,給出改進方案。 [貼上你的程式碼]
AI 使用建議
AI 的重構建議需要你自己驗證——跑測試確認行為沒變。把 AI 當作「提建議的同事」,而不是「無條件信任的權威」。
6. 總結
回顧這一路,我們從識別問題到解決問題,建立了一套完整的程式碼品質改進體系:
- 識別:學會聞到程式碼壞味道,知道哪裡需要改進
- 重構:掌握安全的重構手法,在測試保護下小步改進
- 協作:透過程式碼審查,讓團隊共同守護程式碼品質
- 度量:用客觀指標追蹤程式碼健康度
終極思考
程式碼品質不是一次性的工作,而是持續的習慣。就像保持房間整潔一樣——不是等到亂得不行了才大掃除,而是每天隨手整理。童子軍法則說得好:離開時讓程式碼比你來時更乾淨一點。
延伸閱讀
- 經典書籍:Martin Fowler《重構:改善既有程式碼的設計》是這個領域的聖經。
- 程式碼整潔之道:Robert C. Martin《Clean Code》提供了大量實用的編碼原則。
- 實用工具:嘗試在專案中設定 ESLint + Prettier + Husky,體驗自動化程式碼品質保障。
- 程式碼審查:Google 的 Code Review 指南是業界標竿,值得學習。