跳至主要内容
💬 回報這一頁

功能開關

問題

有一組功能已經寫完、測過、能跑,但現在還不該給使用者看到

  • 相關流程還沒整合完,先出現只會引起誤用
  • 要讓使用者先專注驗收另一塊,多餘的入口會分散注意力
  • 資料還沒備齊,畫面打開會是一片空的

三種常見做法,三種不同的痛:

做法問題
把程式碼刪掉,之後再寫回來「之後」寫回來的是另一個東西。測試、示範資料、邊界處理全部要重來
開一條長期分支不合併分支活得越久,合併那天越痛。而且分支上的程式碼不會被 CI 與其他人的改動照顧到
靠環境區分(開發環境開、正式環境關)正式環境跑的是沒有人真正用過的那個組合。你驗收的版本不等於你發佈的版本

做法:純布林開關,只隱藏入口

export const FEATURES = {
/** 存量清查(清查作業畫面 + 設定分頁 + 結算頁的參考數量) */
quantityCheck: false,
/** 任務追蹤(時數拆分/成本/完成預估 + 工作台的超額警示) */
taskTracking: false,
} as const;

1. 只隱藏入口,不刪程式碼、不刪資料、不動計算

被關掉的功能,它的一切都留著

留著什麼為什麼
狀態切片、示範資料重啟時不必重建資料模型
純函數(計算、彙總)它們有測試,測試繼續跑,不會腐化
元件與畫面Storybook 仍可預覽,設計仍可迭代
資料庫欄位與既有資料關功能不是刪資料。刪了就回不去了

唯一被拿掉的是使用者走得到的路。 這讓開關的成本停留在「幾行判斷」,而不是「一次重構」。

2. 純布林,與環境脫鉤

開關的值是寫死的 true / false所有環境一致

這一點違反很多人的直覺(「不是應該開發環境打開嗎?」),但理由很硬:

為什麼不依環境決定

開關一旦與環境綁定,你驗收的畫面就不是使用者會看到的畫面。 開發環境有 12 個側邊欄項目、正式環境有 9 個—— 那是兩套資訊架構,而其中一套從來沒有被完整走過。

如果目的是「開發時要能看到」,正確做法是改那一行布林值(改完自己看,不提交), 而不是讓程式自己依環境決定。差別在於:前者不會被誤發佈,後者一定會。

3. 把 gating 點列成清單

這是實務上唯一真正會出錯的地方:功能的入口不只有側邊欄那一個

gating 點漏掉的後果
側邊欄項目——(這個不會漏)
路由本身使用者用舊書籤直接開網址,進到一個半殘畫面
未知路徑的 fallback關掉的畫面剛好是預設首頁 → 白屏
其他畫面的跨功能引用(工作台待辦、彙總報表的某一區、結算畫面的參考數值)到處出現空區塊,或是引用不存在的資料而炸掉
設定頁的分頁使用者設定了一個不存在的功能
說明內容文件描述一個找不到的畫面——比壞掉更糟,因為使用者會以為是自己找不到
✅ 這樣做

加開關時,用全庫搜尋找出所有引用該功能的地方,逐一決定要不要 gate, 並把清單寫進開關檔的註解。

🚫 不要這樣

不要只藏側邊欄就宣告完成。跨功能引用是後台系統的常態—— 一個功能之所以值得做,通常正是因為別的畫面會用到它。

4. 前置條件:先證明核心數字零依賴

關掉一個功能之前,要先回答一個問題: 核心計算結果會不會因此改變?

被關掉的資料 ──?──> 核心計算
(若有依賴,關功能=改數字,那不是關功能,是改行為)

驗證方式很直接:搜尋核心計算與選取器對那些資料的引用,確認是。 不是零的話,開關就不是「隱藏入口」而是「改變結果」—— 這種情況要嘛先把依賴切乾淨,要嘛就不要用開關解決。

這條檢查值得寫進提交說明,因為它是下一個人重新打開開關時最想知道的事

5. 邊界要定義精確

同名的東西不見得屬於同一個功能。

舉例:關掉「任務追蹤」功能之後,主檔裡那個叫「任務類別」的自由文字欄位、 以及報表裡依它分組的功能,都應該留著—— 那是一個獨立的分類維度,只是名字剛好一樣。

✅ 這樣做

在開關的註解裡寫明它不包含什麼

🚫 不要這樣

不要靠關鍵字搜尋決定範圍。「凡是出現這個詞的都關掉」會連帶關掉 一個使用者天天在用、而且與該功能毫無關係的欄位。

6. 重啟=改一個布林值

整套做法的驗收標準只有一句:false 改成 true,功能完整回來,不需要其他改動。

做不到的話,代表某個地方的隱藏不是靠開關,而是靠刪除或註解—— 那些地方會在重啟時變成驚喜。

取捨

代價一:被關掉的程式碼仍然要維護。

它會被重構掃到、會被相依套件升級影響、會讓建置產物變大。 這是刻意付的成本——換來的是「重啟」是一個決定而不是一個專案。

代價二:開關會累積。

沒有人清理的開關,三年後會有二十個,而且沒人知道哪些還有意義。 減災方式:每個開關的註解要寫誰決定的、為什麼、什麼條件下打開。 第三項最重要——沒有打開條件的開關,實際上是一個永久刪除, 只是偽裝成暫時的

代價三:測試矩陣變兩倍(理論上)。

實務上不必真的跑兩套:核心計算既然已證明零依賴, 測試只要涵蓋「開關關閉時導覽與路由仍然正確」即可, 其餘測試沿用原本那一套。

反例

✅ 這樣做

開關的粒度=一個使用者認得的功能

🚫 不要這樣

不要用開關包單一按鈕或單一欄位。那不叫功能開關, 那是把「要不要做這個設計」的決定用程式碼記下來——該決定的事就決定掉。

✅ 這樣做

關閉的功能在元件層仍然可被預覽(Storybook), 設計與實作可以繼續推進。

🚫 不要這樣

不要因為「反正關掉了」就停止維護它的 story 與測試。 重啟那天你會發現它已經與現行的元件庫脫鉤了, 而那正是你當初選擇用開關而不是刪除的唯一理由。