🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

重構第三天,我把專案裡 500 個 any 全部換成了具體的 Interface,然後專案崩了😭

0_GvAg7U_pHLYNUJOj.jpg

開始在重構舊專案的最近一個月,我每天打開專案程式碼,心情都像是在上墳😖。

這個專案是5年前的舊程式碼,說是用了 TypeScript,但含 any 量高達 80%。

User 是 any,Response 是 any,連 window 也是 (window as any)。

每次寫業務,我都得猜這個 res.data.list 到底是個陣列,還是個 null,還是後端心情不好傳回來的一個空字串。

作為一個有程式碼潔癖的資深前端,我忍不了😡。

每週五下午,趁著業務需求的空窗期,決定搞個大掃除。

目標是:幹掉核心模組裡的所有 any,還 TypeScript 一個明確的類型。

過程是 - 爽是爽了

那個週末我是在多巴胺的海洋裡度過的。

我對著介面文件,把那些面目可憎的 any 一個個替換成了極其優雅的 Interface。

Refactor 前:

// 屎山程式碼
function renderUser(user: any) {
    const name = user.info.name; // 這裡的 info 可能是 undefined,但 TS 不報錯
    const age = user.age.toFixed(2); // 這裡的 age 可能是 string,TS 也不報錯
    return `User: ${name}, Age: ${age}`;
}

Refactor 後:

// 優雅程式碼
interface UserInfo {
    name: string;
    avatar?: string;
}

interface User {
    id: number;
    info: UserInfo; // 必須存在
    age: number;    // 必須是數字
}

function renderUser(user: User) {
    // 此時 IDE 智能提示全出來了,簡直絲滑
    return `User: ${user.info.name}, Age: ${user.age.toFixed(2)}`;
}

看著 VS Code 裡那一個個紅色的波浪線被我修好,看著 TS 編譯通過的綠色對勾,我感覺自己就是程式碼界的上帝🥱。我甚至順手把以前程式碼裡那些醜陋的像 if (user && user.info) 防禦性判斷給刪了—— 因為 Interface 定義裡寫了,info 是必選屬性,不可能為空!

週一早上,我信心滿滿地提了 Merge Request,順便把程式碼推上了測試環境。

我心想:兄弟們,以後你們調介面都有智能提示了😁。

事故發生了:P1 級白屏,我在群裡被 @ 成了篩子

上午 10 點的時候,測試環境崩了。

image.png

image.png

上午 10 點半,技術總監衝到我工位:你動核心模組了?怎麼列表頁全白了?控制台全是報錯!

我一愣:不可能啊,我本地編譯全通過了,TS 類型檢查也是完美的。

打開控制台一看,我傻眼了。滿屏紅字:

  • Uncaught TypeError: Cannot read properties of undefined (reading 'name')
  • Uncaught TypeError: user.age.toFixed is not a function

image.png

怎麼會?

我明明定義了 interface User,裡面寫死了 info 必須存在,age 必須是 number 啊!

真相是,原來 TypeScript 是最大的騙子😡

經過兩個小時的狼狽排查,我終於明白了那個讓所有 TS 新手都會摔坑的真理:

TypeScript 的類型,在運行時(Runtime)就是個屁!

後端的嘴,騙人的鬼

Interface 定義裡,我信了後端的文件,寫了 age: number。

但實際上,老數據裡有幾百條數據,age 存的是字串 "18"😡。

以前用 any 的時候,JS 隱式轉換還能跑(或者之前壓根沒調 toFixed)。

現在我為了規範,加了 .toFixed(2),因為 TS 告訴我它是數字。

結果運行時,瀏覽器拿著字串 "18" 去調 toFixed,直接炸穿😥。

必選屬性莫名其妙的消失

Interface 裡我寫了 info: UserInfo(必選)。

於是我自信地刪掉了 if (user.info) 的判空邏輯。

結果後端返回的數據裡,因為歷史髒數據,真的有幾個用戶的 info 是 null😡。

TS 編譯時很開心,瀏覽器運行時直接拋出 Cannot read properties of null。

我把編譯時的類型安全,誤當作了運行時的數據安全。

我以為我在寫 Java,其實我還在寫 JavaScript。TS 編譯成 JS 後,那些漂亮的 Interface 全都被擦除得乾乾淨淨,裸奔的數據依然是那個爛樣子。

反思反思反思:這 500 個 any,原來是護身符😥

看著回滾後的程式碼,那個醜陋的 any 重新回到了螢幕上,我竟然感到了一絲安全感。

這次事故給了我三個血淋淋的教訓:

別太迷信文檔,要信數據。

後端的 Swagger 文檔寫寫而已,你真信了 Required,上線就得背鍋。

TS 是協定,Zod 才是嚴格的。

如果你真的想保證數據類型安全,光寫 Interface 沒用(那只是給 IDE 看的)。你得上運行時校驗庫(比如 Zod 或 Runtypes)。

// 這才是真安全
const UserSchema = z.object({
    age: z.number(), // 運行時如果拿到 string,這裡直接拋錯,而不是等到業務邏輯裡炸開
});

防禦性編程永遠不要刪 !!!

不管 TS 告訴你這個字段多一定存在,只要數據源來自網路(API),老老實實寫 Optional Chaining (user?.info?.name)。

最後的結局

那天下午,我默默地把自己寫的那些 interface 改成了原樣:

interface User {
    // 認怂了,全部改成可選 + 聯合類型😒
    age?: number | string; 
    info?: UserInfo | null;
}

雖然程式碼裡又要加回那些煩人的 if 判斷,雖然類型提示沒那麼完美了,但至少——它不崩了😒。

如果你也想重構專案裡的 any,聽哥一句勸:

除非你上了 Zod 做運行時校驗,否則那個醜陋的 any,可能是你專案裡最後一道防線。

分享完畢🙌

謝謝大家.gif


原文出處:https://juejin.cn/post/7596639774266671146


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝7   💬8   ❤️1
195
🥈
我愛JS
📝1   ❤️2
24
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付