這條雷不是「炸」的,是「勒」的——它沒有造成任何一次事故,它造成的是每次改動都痛一次、綿延好幾個月的技術債利息。
服務最初的訂閱模型很單純:免費 vs 付費。於是使用者表上有了一個再自然不過的欄位:
is_premium BOOLEAN
半年後,商業模式演化成三個以上的等級(免費、標準、高階,中間還一度出現過一個後來停售、但既有訂戶要繼續維持權益的歷史等級)。這時候,散落在整個程式碼庫裡幾十處的 if user.is_premium: 全部變成了錯誤的抽象。
is_premium 的問題不是它「錯」,是它把一個一維的階梯壓扁成了一個 bit。等級制度的本質是有序的:高階使用者理應涵蓋標準等級的所有權益。但布林旗標表達不了「涵蓋」,於是擴充等級時每一處判斷都要人工重新回答:這裡的 is_premium 當初想問的到底是「至少是付費使用者」還是「是最高等級使用者」?——同一個欄位在不同地方被賦予了不同的隱含語意,這才是最深的坑。
還有停售等級的問題:那個歷史等級的訂戶,is_premium 該是什麼?無論填什麼都會在某些判斷上給他錯誤的權益。布林世界裡根本沒有這個等級的位置。
重構方向是把「等級」升格為一等公民:
class Tier(IntEnum):
FREE = 0
LEGACY = 1 # 停售等級:權益凍結在當初的承諾
STANDARD = 2
PREMIUM = 3
# 所有權限判斷改成門檻式比較
if user.tier >= Tier.STANDARD:
...
配合一份「等級 → 權益」的對照表(Day 14 細講),把散落的判斷收斂進去。這次大範圍重構是跟 agent 協作完成的:它負責找出所有 is_premium 的使用處、逐一判讀原意、改寫成門檻比較——逐處判讀原意這一步無法全自動,每一處我都 review 過,因為那正是「同一欄位不同隱含語意」的排雷過程。
寫下任何布林欄位之前,問一個問題:這個「是/否」有沒有可能其實是一個階梯的、目前只有兩階的特例? 身份等級、方案類型、狀態機——這些概念裡的「二選一」幾乎都是暫時的。列舉型別在只有兩個值的時候跟布林成本幾乎相同,但它給未來留了位置;布林沒有。
順帶寫進地雷清單的原話是:「is_premium 布林旗標是加訂閱等級時最大的雷。」一句話,救了後來每一次跟等級有關的開發。