文藝復興的畫室,終究會關門
師傅會老去,畫室的招牌,總有一天會摘下來
但工藝精神,沒有跟著招牌一起消失
它活在每一位曾經在那裡磨過顏料的學徒身上
他們有些人,後來自己開了畫室,把同一套判斷力,教給下一代學徒畫室會關,判斷力不會,這正是這 30 天,想留給你的東西
| 模組 | 隱喻 | 核心提醒 |
|---|---|---|
| 模組一・臃腫者 | 布魯內雷斯基的圓頂 | 比例,先於體積「大,不是問題;失衡的大,才是問題」 |
| 模組二・物件導向的濫用者 | 達文西的透視法 | 先懂結構,才畫得對形體「型別與繼承要用在對的地方」 |
| 模組三・變更的妨礙者 | 濕壁畫的時間壓力 | 顏料乾了之後,就改不動了「今天的設計,決定明天改得動、改不動」 |
| 模組四・可有可無者 | 阿伯提的節制美學 | 多餘的裝飾不是美,是負擔「敢刪,比敢加更需要判斷力」 |
| 模組五・過度的耦合者 | 畫室的分工倫理 | 師傅與學徒,不該互相代筆「邊界清楚,合作才走得久」 |
23 種壞味道,看似各自獨立,其實只是同一種能力的不同切面:判斷「這段程式碼,寫得好嗎?」
回頭看這一路走過的例子,會發現大部分壞味道,都能歸進幾個更根本的類別:
下次你在 Code Review 裡覺得「這裡怪怪的」,卻說不清楚哪裡怪,先問自己一句:
這段程式碼,落在這四種病灶裡的哪一種?
命名問題,通常一個好名字就能解
邊界問題,需要重新想清楚職責
耦合問題,需要拉開類別之間的距離
清理問題,只需要一份刪除的勇氣
Day 01 那段 AI 生成的 OrderProcessor,當時只點出三個警訊
三十天走下來,你手上的詞彙變多了,回頭看同一段程式碼,能認出的警訊,不只三個
// 同一段 Day 01 的程式碼,三十天後,你看得出比第一次更多警訊
public class OrderProcessor
{
private readonly AppDbContext _db; // 依賴具體實作而非抽象介面(對照 Day02.DIP)
private readonly ILogger _log;
private readonly IEmailService _emailService;
private readonly ISmsService _smsService;
public OrderProcessor(AppDbContext db, ILogger log,
IEmailService emailService, ISmsService smsService)
{
_db = db;
_log = log;
_emailService = emailService;
_smsService = smsService;
}
public decimal Process(int customerId, int productId, int qty, // 七個參數(Long Parameter List,Day06)
string customerType, string couponCode, bool sendEmail, bool sendSms)
{
// 驗證、算庫存、算折扣、寫 log、發通知——全部擠在同一個方法裡(Long Method,Day03)
var customer = _db.Customers.Find(customerId);
var product = _db.Products.Find(productId);
if (customer == null || product == null)
throw new InvalidOperationException("not found");
if (product.Stock < qty)
throw new InvalidOperationException("out of stock");
decimal price = product.Price * qty;
if (customerType == "VIP") // 字串代表會員等級(Primitive Obsession,Day05)
price = price * 0.9m;
else if (customerType == "REGULAR" && couponCode == "WELCOME10") // 兩個分支,是 Switch Statement 的早期形狀(Day09)
price = price * 0.95m;
product.Stock = product.Stock - qty;
_db.SaveChanges();
_log.Info("order processed: customer=" + customerId + " ...");
if (sendEmail) _emailService.Send(customer.Email, "Order Confirmed", "...");
if (sendSms) _smsService.Send(customer.Phone, "...");
return price;
// 庫存規則、折扣規則、通知規則,任何一個改變都要動這個方法(Divergent Change 前兆,Day15)
}
}
同一段程式碼,這次看出了六個警訊:
| 警訊 | 所屬模組/Day | 現在會不會算錯結果 |
|---|---|---|
| 字串代表會員等級 | 模組一.Primitive Obsession(Day 05) | 會,打錯字不會編譯錯誤,只會悄悄算錯折扣 |
| 方法做五件事 | 模組一.Long Method(Day 03) | 不會,但每加一種需求,改動風險都在往上疊 |
| 七個參數 | 模組一.Long Parameter List(Day 06) | 不會,是 Long Method 的副產物 |
依賴具體 AppDbContext |
對照 Day 02.DIP 依賴反轉 | 不會,但這段邏輯現在幾乎沒辦法寫單元測試 |
| 兩個分支的 if-else | 模組二.Switch Statements 前兆(Day 09) | 不會,目前只有兩種會員等級 |
| 只有一種變動理由真的發生過 | 模組三.Divergent Change 前兆(Day 15) | 不會,目前只有折扣規則真的改過 |
如果照 Day 01 提到的順序修,先拆 Long Method,再處理參數列表,最後才輪到字串比對
你會先花時間重新設計方法結構,而 production 裡的折扣,可能已經因為一次打錯字,悄悄算錯了好幾天
修的順序,不該照警訊被發現的順序,該照風險排
用 Day 18 那套「現在修或先記一筆」的判斷邏輯,套在這六個警訊上,會排出四層:
AppDbContext,等真的需要幫這段邏輯寫單元測試,或真的要換資料存取方式,再處理六個警訊,只有一個現在真的要修,這正是判斷力要做的事
「不是把每一種味道都聞出來就好,是聞出來之後,還能說清楚哪一個先處理」
這系列的每一篇文章,都在重複同一個節奏:
這五個問題,不是用來考試的知識點,是拿來在真實 Code Review 裡
把「我覺得怪怪的」翻譯成「這裡是 XX 壞味道,建議用 YY 重構手法處理」的共同語言
有共同語言,團隊才吵得起有意義的架,不是吵「你的風格我不喜歡」
是吵「這個設計,五年後會不會變成一場災難」
CalculateXxx 好像更該搬到 Order 裡」有一條簡單到近乎樸素的原則,比任何複雜的規範都更管用:
每次碰到一段程式碼,讓它,比你接手時更乾淨一點。
不用一次重寫整個模組,才叫改善
修掉一個容易誤導人的命名、拆掉一段過長的方法、刪掉一段沒人用的程式碼
每一次微小的動作,都在為下一位路過這裡的人,省下重新理解一次的成本
這正是文藝復興工藝精神的核心
不是靠一次偉大的傑作定義一位工匠,是靠日復一日,對手上這塊畫布的認真程度
系列開頭提過:AI 讓寫程式碼的速度,前所未有地快
這不會讓 Code Smell 過時,反而讓它更重要,當產出程式碼的成本趨近於零
唯一還需要人類花時間把關的,就是 「這段程式碼,值不值得留下來」 這個判斷
AI 可以幫你把顏料調好、把畫布繃平,但「這幅畫,構圖好不好、比例對不對、有沒有多餘的裝飾」
這份判斷力,還是得靠人
謝謝你陪這個系列,走完 30 天
從布魯內雷斯基的圓頂,到畫室裡收工前的最後一眼
這段旅程談的,始終不是某一種特定的程式語言或框架,是一種可以帶著走的判斷力
下一次你打開一段程式碼,覺得「這裡,有點味道」的時候
希望你已經有了足夠的詞彙,把那股直覺,講清楚,也有了足夠的手法,把它變得更好
畫室的門,會關;但工藝精神,留在每一次你認真對待的 commit 裡