重構不是一定要大改特改,要你把整個架構打掉重練,而是小步小步且精確的日常整理。
也別為了重構而重構。
如果你的程式碼能跑,沒有 Bug,而且所有人都懂,那就別動它。
只有當你看到「味道」,也就是你覺得這段程式碼很難讀懂、很難修改、很難擴充時,那才是重構的時機。
再來變臭的原因是資料結構設計不良,以及對特殊情況的處理方式。
這三個原則把握好:
資料結構優先:程式碼圍繞資料運轉,而不是反過來。
消滅分支:好程式碼沒有特殊情況。
實用主義:做對業務有意義的事,而不是為了抽象而抽象。
重構的主要原則:先立安全網,再小步前進。
重構的目的是在不改變程式碼外部行為的前提下,提升其內部品質。
這麼做能讓程式碼更易於理解(可讀性
)、更易於維護(可維護性
)和更易於擴展(可擴展性
)。
重構不是浪費時間,而是為了節省未來更多尋找 Bug、開發新功能的時間,人生應該花在更有意義的地方。
加測試:這是唯一能讓你晚上睡得著覺的東西。如果你要動的程式碼沒有測試,那就先寫一個,因為找 bug 的時間比這個長多了。
小步修改:一次只動一個東西。你的每個 commit
應該都小到能一眼看完,並且清楚地表達「我做了什麼」。
運行測試:每次小改動後,立刻運行測試。確保你沒有改錯任何東西。這是一個簡單的行為驗證,不是什麼複雜的儀式。
發臭的程式碼總是在處理特殊情況。
// 🔴 臭味:這個函式做了兩件事,因為資料結構設計得不好
function save(user, db){
// 1. 處理資料
const normalized = {
id: user.id,
email: user.email.trim().toLowerCase(),
};
// 2. 存進資料庫
return db.insert(normalized);
}
save()
這個函式,它的名字叫 save
,它的職責應該是「存」。
但它在裡面做了資料清理(trim()
、toLowerCase()
)。
單一職責原則,不只是一個函式只做一件事,還有「函式只做它名字裡說的事」。
// 🟢 好味道:這才叫做單一職責
function createNormalizedUser(u) {
// 這就是它的唯一職責
return {
id: u.id,
email: u.email.trim().toLowerCase(),
};
}
// 這個函式現在可以只做一件事,因為它處理的資料是乾淨的
function save(user, db){
// 這才是真正的 save
return db.insert(user);
}
這就是重新設計資料結構來消除分支 ,會比「多型」或者「繼承」強一萬倍。
資料結構優先:先把資料整理好,讓它變成一個「乾淨的」使用者物件。這段程式碼創建了一個新的函式 createNormalizedUser
來處理這個問題。
消滅分支:現在 save
函式沒有任何多餘的事情做,它永遠只做 db.insert()
這件事。
一個味道一個分支:每次重構只針對一個「程式碼臭味」,並在一個獨立的分支上完成。
PR 只做一件事:在 Pull Request 中,只包含單一、原子化的重構提交(commit)。你的 commit
應該比你的晚餐還小。
詳細描述:在 PR 描述中,清楚說明這次重構的動機
、做法
、風險
,以及如何回滾
。
風險管控:對於風險較大的重構,先加上遙測(telemetry)與功能開關(feature flag),以便在出現問題時能快速回滾,而無需部署新的程式碼。
最重要的一點: 永遠不要破壞現有的功能。如果你的重構導致任何使用者功能崩潰,那就說明測試不夠,或者根本不明白自己在做什麼。
是否有測試保護?
每一步是否都可回滾?
命名是否比重構前更清楚?
程式碼的複雜度是否下降?
把重構當成日常清潔,持續去味,重構不是為了讓程式碼看起來更漂亮,而是為了讓它更可靠。就這麼簡單。