iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Modern Web

一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年系列 第 24

Day 24|最好的一次重構,刪掉的是新增的十四倍

  • 分享至 

  • xImage
  •  

模組五|重構期的品質與節奏(Day 22–26)

我把新專案裡「刪除行數遠大於新增行數」的改動排出來(已濾掉自動產生的檔案):

新增 刪除 內容
+846 −11,885 一個設定彈窗的重構
+3,039 −4,521 訂單詳情模組重構
+11 −1,934 多個彈窗開關變數合一
+180 −1,249 彈窗合併進通用元件
+0 −1,748 刪掉框架的範例路由
+0 −485 刪掉框架的範例表單

第一名:新增 846 行,刪掉 11,885 行。比例是 1 比 14。

第三名更誇張:新增 11 行,刪掉 1,934 行。

這不是巧合,是一種明確的品味。今天講這種品味長什麼樣。

三種刪減手法

一、把只有一處在用的子元件,併回主檔

這是最常見的一種,也是最反直覺的。

一般的認知是「檔案拆得越細越好」。但實際上,如果一個子元件:

  • 只被一個地方使用
  • 而且跟主檔的狀態耦合很緊(要傳一堆 props、又要 emit 一堆事件回去)

那它的存在只帶來一件事:閱讀時要多跳一個檔案。

你在讀一份食譜,讀到第三步的時候寫著「請參閱附錄 B」。

你翻到附錄 B,發現那裡只有一句話:「把火轉小」。

這句話為什麼不直接寫在第三步?

如果附錄 B 被十個步驟引用,那它值得存在。如果只有一個,那它只是讓你多翻了一次書。

判斷標準很簡單:被幾個地方用? 一個的話,就該考慮併回去。

二、把一組布林值合成單一狀態

這個槽一次只放得下一顆,我把原本散在地上的十幾顆全收了起來

那個「新增 11 行、刪掉 1,934 行」的改動,做的就是這件事。

典型的舊寫法:

// 一個彈窗一個開關
const showEditDialog = ref(false)
const showDetailDialog = ref(false)
const showHistoryDialog = ref(false)
// ...再來十幾個

然後每個地方開關的時候,都要記得把其他的關掉,否則會出現兩個彈窗疊在一起。

於是每個開關函式裡都有一串「把別的設成 false」。十幾個開關,就是十幾組互相關閉的程式碼。

改成單一狀態之後:

const activeDialog = ref<'edit' | 'detail' | 'history' | null>(null)

互斥變成語言本身保證的事,那十幾組互相關閉的程式碼全部消失。

這是我最喜歡的一種刪減:不是把程式碼寫短,是換一個讓錯誤不可能發生的表達方式。

三、刪掉框架送的範例

前面那兩筆 +0 的改動,是專案最早期就做的(Day 12 講過)。

框架附的範例路由、範例表單,加起來兩千多行。它們不會造成 bug,但會進搜尋結果、被新人當成慣例照抄。

新增零行、刪除兩千行,這種改動的價值不在當下,在往後每一次搜尋。

但有一種東西要留下來

講完刪,講不能刪的。

專案裡有這樣的程式碼:

// 暫時關閉拖拽功能,等下一版再上
// dragEnabled: true,

被註解掉,但寫了原因。

這跟「死碼」不一樣。死碼是沒有人知道為什麼還在的程式碼;而這個是有意識的暫緩。

差別在那一行註解。有註解的,下一個人知道能不能刪;沒註解的,下一個人只能留著。

我們的規則因此是:

要留的東西,一定要寫原因;沒有原因的,直接刪。

而且原因要寫「什麼時候可以刪」,不只是「為什麼留」。上面那個「等下一版再上」就有這個性質——它自帶一個到期日。

(誠實補充:這種帶原因的暫留碼在專案裡只有個位數個。大部分被註解掉的程式碼,還是沒有原因的。 規則訂了,執行率不高。)

四、順手刪掉「移除功能」本身

還有一種刪減比較少見,但我覺得值得單獨講:重構的過程中,順手拿掉不再需要的功能。

翻改動紀錄的時候,我看到好幾筆是這種形狀:

  • 移除某個搜尋條件,簡化搜尋表單
  • 移除某個重設功能,簡化操作介面
  • 某個設定只保留在其中一套系統,另一套整個拿掉

這些不是「換個寫法」,是真的少了一個按鈕。

而它們能發生,是因為重寫的時候你會被迫問一個平常不會問的問題:「這個功能真的有人在用嗎?」

平常沒有人會為了刪一個搜尋條件開一張票,投報率太低。但當你已經在重寫這一頁了,順手拿掉的成本趨近於零。

遷移是產品瘦身成本最低的時刻(Day 14 從模組層級講過這件事,這裡是欄位與按鈕層級的版本)。

這件事有個名字:童子軍法則

離開營地的時候,讓它比你來的時候更乾淨一點。

重點在「一點」。它不是要求你每次去露營都做一次大掃除,是要求你順手。

實務上的形狀是:你為了修一個 bug 打開某個檔案,順手把旁邊那個沒用到的 import 刪掉、把那段註解掉三年的程式碼清掉。

單獨看,每一次都微不足道。 但如果一個團隊十個人、每人每天打開三個檔案,那就是每天三十次微小的清理。

而反過來也成立:如果每個人都覺得「這不是我的責任」,那檔案只會單向地長胖。

一個我差點寫錯的地方

我伸手往那堆泡沫壓下去,看起來很高的東西一下就沒了

我第一次拉「刪除最多」的排行榜時,前四名長這樣:

+9,236  −124,591
+167    −81,453
+0      −81,374

十二萬行!八萬行!我差點就把這個當成頭條寫出來。

然後我去看了那些改動的內容——它們是清理自動產生的快照檔。

那些檔案是某個工具產生的中間產物,本來就不該進版控。有人發現之後,把它們從版控裡移除、改成「用完即丟」。

這是好事,但它不是重構。它是清理垃圾。

如果我把它當成「我們刪了十二萬行程式碼」寫出來,那是在誇大成果。濾掉自動產生的檔案之後,真正的頭條才浮出來——那個 1 比 14。

量化重構成果的時候,第一步是先確認你量的是不是「人寫的程式碼」。

代價

一、刪減型的重構,在數字上看起來像「沒有產出」。 一個週期下來新增行數是負的,如果團隊用產出量衡量績效,這種工作沒有人想做。

二、合併子元件回主檔,會讓單一檔案變長。 那個 1 比 14 的改動之後,主檔確實變大了。這是把「檔案數量」換成「單檔長度」,我認為值得,但它確實違反了「檔案要小」這個常見的直覺。

三、「刪掉沒人用的東西」需要證明沒人用。 這件事的成本在 Day 4 和 Day 14 都講過,這裡再次成立。我們有一批東西是「知道可以刪、但還沒證明」的狀態,它們就一直躺著。

帶走什麼

  1. 衡量重構成效,看行數變化的方向。 如果每次重構都在長胖,那可能只是在加功能。
  2. 只被一處使用、又跟主檔耦合緊的子元件,該併回去。 拆分的價值來自複用,沒有複用就只剩跳轉成本。
  3. 最好的刪減不是把程式碼寫短,是換一個讓錯誤不可能發生的表達方式。 十幾個布林值換成一個狀態,就是這種。
  4. 要留的程式碼一定要寫原因,而且原因要包含「什麼時候可以刪」。 沒有原因的註解碼,等於留給下一個人的無解謎題。
  5. 量化成果前,先確認你量的是不是人寫的程式碼。 自動產生的檔案會讓數字好看得不真實。

明天 Day 25 講相反的一面:有些醜東西,我們知道它醜,但決定不改。 包括一個拼錯的欄位名,和一個會讓使用者被踢出系統的錯誤碼。


上一篇
Day 23|將近一半的改動是在修 bug,而我認為這是好消息
下一篇
Day 25|有一個欄位名拼錯了,而我們決定不改
系列文
一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言