iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
Modern Web

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

Day 2|先量體重再談減肥:把「這 code 很爛」翻譯成一份清單

  • 分享至 

  • xImage
  •  

模組一|現況盤點與立案(Day 1–5)

昨天說我接手後的第一件事不是寫程式,是盤點。今天把方法攤開。

「我覺得這 code 很爛」是一句沒有殺傷力的話。 它聽起來像抱怨、像品味之爭。你講完,對方最多說「那你有空整理一下」,然後這件事就結束了。

盤點要做的是把同一件事翻譯成另一種語言:可驗證的數字、可比較的趨勢、可估算的成本。這種話術不需要對方懂前端,只需要他會看數字。

而且盤點還有一個對你自己的作用:它會告訴你哪裡最痛,而那通常不是你以為的地方。我原本以為該優先處理那幾個兩千行的巨型元件,後來改了想法。

六個維度

我盤了六個維度,每個維度都會給實際跑的指令和跑出來的數字。指令都是 shell 內建的,不需要裝任何工具,因為要求別人先裝三個 npm 套件才能盤點,本身就是一種摩擦。

以下數字全部來自我們其中一套舊系統(Vue 2,六年歷史)。

維度一:規模

# 元件檔數量
find src -name '*.vue' | wc -l

# 總行數
find src -name '*.vue' -exec cat {} + | wc -l

# 最長的 5 個檔案
find src -name '*.vue' -exec wc -l {} + | sort -rn | sed -n '2,6p'

我們的結果:

指標 數字
.vue 檔案數 342
總行數 103,870
平均每檔 303 行
最長的檔案 2,553 行

「平均每檔 303 行」這個數字比總行數更有訊息量。一個好維護的 Vue 元件大概在 100–200 行之間,平均值到 303 已經偏高。

但平均值可能是「大家都稍微胖一點」,也可能是「大部分正常、少數是怪物」,兩者的處理方式完全不同。

維度二:怪物分佈

平均值會騙人,所以要看尾巴:

# 超過 800 行的檔案有幾個
find src -name '*.vue' -exec wc -l {} + | awk '$1 > 800 && $2 != "total"' | wc -l

結果出來:23 個檔案超過 800 行,前五名分別是 2,553 / 2,419 / 2,219 / 2,065 / 1,754 行。

所以答案是後者:大部分檔案其實還好,但有二十幾個怪物,我們不需要全面翻修。

長條圖:檔案行數分佈,橫軸是行數區間、縱軸是檔案數,標出 800 行門檻線與超標的 23 個檔案

這張圖要看的不是「有幾個大檔」,是尾巴的長度:主體集中在 300 行以下,但右邊拖出一條稀疏的長尾,一路拉到 2,553 行。平均值 303 之所以騙人,就是被這條尾巴拉上去的。

800 這個門檻是我自己抓的,理由很土:超過 800 行,你在 IDE 裡就沒辦法靠捲動掌握全貌了。你可以用任何門檻,重點是要有一個門檻,而且要說得出理由。

維度三:樣板重複度

admin 系統最大的成本不在複雜邏輯,在同一套東西寫了幾百遍。

# 表格欄位總共出現幾次
grep -rho 'el-table-column' src --include='*.vue' | wc -l

# 有幾個檔案裡有彈窗
grep -rl 'el-dialog' src --include='*.vue' | wc -l

我們的結果:表格欄位出現 3,354 次、105 個檔案裡有彈窗(共 383 個)。

翻譯成日常工作是這樣的:

產品說:「所有表格裡沒有資料的欄位,請顯示一個橫線,不要留空白。」
這聽起來像一個五分鐘的需求。但因為每個表格欄位都是各自手寫的,你要去改 3,354 個地方。

如果這些表格共用同一個元件,這個需求就是改一個檔案。這 3,354 就是「沒有共用層」的價格標籤,而它每次有類似需求就要付一次。

提醒一個容易踩的坑:grep -c 算的是符合的行數,不是出現次數。一行裡有兩個就會少算一個。要算出現次數得用 grep -o 把每個結果拆成一行,再 wc -l。我第一次盤的時候就是這裡算少了,數字差了將近一倍。

維度四:元件之間在偷偷牽誰的手

這個維度最重要,也最少人盤:改了 A,會不會害 B 壞掉?

Vue 元件之間溝通有兩種方式:

  • 正門:父層用 props 把資料傳下去,子層用 emit 把事件送上來。這條路是明的,改了會有跡可循。
  • 後門:父層用 this.$refs.xxx.someMethod() 直接伸手進子元件裡呼叫它的方法。很方便,但繞過了所有約定。

後門用多了會怎樣?數數看:

# 用後門的次數
grep -rho 'this\.\$refs' src --include='*.vue' | wc -l
# 有幾個檔案在用後門
grep -rl 'this\.\$refs' src --include='*.vue' | wc -l

我們的結果:330 次,分布在 132 個檔案。342 個元件裡,將近四成在走後門。

為什麼這個數字比行數更可怕?舉個場景:

你今天要重構一個子元件,把裡面的 refresh() 改名成 reload()。你存檔、跑起來、自己那頁一切正常,於是你送出。三天後客服說某個報表頁的重新整理按鈕沒反應了,因為那頁的父元件裡有一行 this.$refs.table.refresh(),而沒有任何工具會告訴你這件事。

這就是差別:行數多只是難讀,走後門多是難改,而且它不會在你改的時候報錯。

這件事有個名字:迪米特法則

迪米特法則(Law of Demeter),別名叫 「不要跟陌生人說話」:只跟你的直接鄰居說話,不要伸手穿過鄰居去碰更遠的東西。

用一個生活場景就懂:

你要跟隔壁鄰居借醬油。

正常的做法:按門鈴,說「可以借我醬油嗎」,鄰居去拿給你。
違反法則的做法:你自己開門進去,走到廚房,打開第二個櫃子的第三層,拿走醬油。

第二種比較快,對吧?直到有一天鄰居重新裝潢,把醬油移到冰箱旁邊,然後你就在他家廚房裡撞牆了。

props / emit 是按門鈴:我告訴你我要什麼,你決定怎麼給我。
this.$refs.table.refresh() 是自己開門進廚房:我知道你家櫃子怎麼排的,我自己拿。

而 330 次的意思是:這個專案裡有 330 個「我知道你家櫃子怎麼排」的秘密約定,沒有一個寫在文件上。 那個知識只存在於當初寫它的人腦袋裡,而那個人可能已經離職了。

我盤點的當下並不知道這個名字,只覺得「這樣改東西很怕」。但知道之後有個好處:下次看到「迪米特法則」,你就會想起有人在別人家廚房撞牆的畫面。

(JavaScript 沒有型別檢查,這種錯誤更容易漏掉,這也是後來導入 TypeScript 的理由之一,Day 10 會講。)

同一個維度還可以順手抓:全域掛載(Vue.prototype)、原型污染(Array.prototype)、全域事件匯流排。這三個 Day 4 會用一整篇拆開講。

維度五:依賴健康度

node -e "const p=require('./package.json');\
console.log('deps:', Object.keys(p.dependencies||{}).length);\
console.log('devDeps:', Object.keys(p.devDependencies||{}).length)"

我們有 59 個執行時依賴 + 44 個開發依賴,共 103 個。

但數量不是重點。要打開 package.json 人工掃一遍,找這三種東西:

  1. 停在很舊的版本:我們的主框架停在六年前的版本;負責發送 API 請求的套件版本號還是 0.x 開頭,而 0.x 在慣例上代表「還沒穩定」,它已經六年沒動了
  2. 兩個套件在做同一件事:我們同時裝了兩套不同的 Excel 處理庫,而且兩套都有檔案在用。這代表當年有人不知道已經有一套,就自己裝了另一套
  3. 版本號寫成 latest:意思是「每次安裝都給我最新的」,聽起來很好,實際上代表兩個同事在不同時間安裝可能拿到不同版本,出問題時很難重現

第 2、3 點都不是「舊」的問題,是沒人在管的問題。舊還可以說是沒空升級,沒人管則代表這個專案已經沒有主人了。

維度六:安全網

find tests -name '*.spec.js' | wc -l

我們的答案是 1。

342 個元件檔,一個測試檔。這個數字我在任何會議裡都不需要解釋:任何人改任何東西,都只能靠肉眼和運氣

最有力的一招:把兩個維度疊在一起看

上面六個維度都是「此時此刻的照片」,真正說服我自己的是加上時間:一個檔案很長不見得是問題,沒人動它就只是躺在那裡;真正危險的是「又長、又每個月都要改」的檔案。

文氏圖:最長的六個檔案與近兩年被改最多次的六個檔案,交集是 6,兩個圓完全重疊

我原本預期兩張榜單會有三、四個重疊,畫出來才發現是六個全中。這張圖之所以值得單獨畫,是因為它把「該從哪裡下刀」這個爭論直接結束掉了,不需要投票,兩個獨立的量化維度已經指向同一批檔案。

而「每個月都要改」這件事,git 已經幫你記了六年:

git log --format='' --name-only --since='2 years ago' -- 'src/*.vue' \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -10

--name-only 讓 git 只印出每次改動碰到的檔名,後面那串就是把檔名排序後數次數,再由多到少排。)

我們近兩年被改最多次的前六名,分別被改了 79、72、72、69、66、63 次。

然後把這份「最常改」的名單跟前面「最長的六個檔案」做交集:

comm -12 <(sort longest.txt) <(sort hottest.txt) | wc -l

答案是 6。最長的六個檔案,就是被改最多次的六個檔案,一個都沒漏。

這個結果殺死了一個常見的反駁:「那些大檔案反正沒人動,先放著吧。」不是沒人動,是所有人每個月都在動它們。最難讀、最容易改壞、改動最頻繁,風險三重疊加

從那一刻起,「重構那幾個兩千行的檔案」就不再是美學主張,而是風險管理。

知道「這是問題」,跟知道「哪一個正在流血」,是兩件事。 而後者只有 git 知道。

兩種痛,方向剛好相反

這張交叉圖還修正了我一開始的判斷:真正該優先的不是「最長的」,是「又長又燙手的」。

盤完之後我還注意到一件事:維度三和「熱點」量到的,其實是兩種方向相反的問題。

  • 一個需求,要改很多檔案。 我們的表格空值規則就是:一個需求、3,354 個修改點。這代表該集中的東西散開了。
  • 一個檔案,因為很多不同理由被改。 那六個熱點檔案就是:每次被改的原因都不一樣。這代表該拆開的東西擠在一起了。

分清楚很重要,因為處方是相反的:前者要「合起來」(抽共用元件),後者要「拆開」(把巨型檔案切分)。方向搞反,你會越改越糟。

而我們兩種都有。

盤完之後,怎麼講

我最後給出去的不是這篇文章這麼長,是一頁:

  • 三個數字:342 個元件檔 / 23 個超過 800 行 / 1 個測試檔
  • 一張交叉圖:最長 = 最常改,六個全中
  • 一句翻譯:「我們最常改的六個檔案,也是最難改的六個檔案,而且沒有測試接住。」
  • 一個成本對照:同一個需求現在要做兩次(因為有兩套系統,Day 3 會講)

這一頁比任何架構圖都有效,因為它講的不是「程式碼多醜」,是「我們每個月都在冒什麼風險」。

代價

盤點不是免費的,有兩個成本要先知道。

一、它會花掉一到兩天,而且交不出任何功能。 這段時間你在跑指令、讀原始碼、做表格,看起來完全沒有產出。如果你的團隊正在趕交期,這件事需要先講好,否則你會在第二天被問「你到底在忙什麼」。

二、盤點報告很容易被讀成批鬥。 那些數字背後是同事六年、在各種交期壓力下的心血,而你拿著一張「23 個超大檔案、1 個測試檔」的表走進會議室,不管你多小心,都有人會覺得你在說「以前的人做得很爛」。

我的處理是:報告裡不寫「誰寫的」、不寫「這寫得很爛」,只寫數字和風險,你要的是預算,不是道歉。 但這個風險沒辦法完全消除,只能降低。如果團隊氣氛本來就緊繃,可以先私下給直屬主管看,而不是在大會上簡報。

一個提醒

數字要能被重跑。 我把所有指令存成一個 shell 檔放進 repo,因為半年後一定會有人問「所以有比較好嗎」,那時候你需要的是同一把尺,不是新的印象。


明天 Day 3,講一個我在盤點時發現、但當下不敢相信的東西:兩套系統裡,有 20 個檔案的 diff 結果是 0 行。以及更可怕的:另外幾個檔案已經開始不一樣了。


上一篇
Day 1|342 個元件、1 個測試檔:一個活了六年的 Vue 2 後台
系列文
一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年2
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言