為什麼選 JavaScript,不選 TypeScript
Orbital Express 使用純 JavaScript。這是一個深思熟慮的永久決定——不是疏忽,也不是「之後再加 TypeScript 支援」的計畫。這頁說明原因。
TL;DR
TypeScript 解決的那類問題(編譯時的型別錯誤),並不是新創公司或成長中的產品團隊最昂貴的問題。它帶來的額外負擔——在配置、入職、除錯、招聘和日常開發上——所付出的代價,超過它節省的成本。我們在規模化的環境下做過這個實驗。算起來划不來。
TypeScript 實際上解決了什麼
TypeScript 在編譯時捕捉型別錯誤。這確實有用。如果你在一個可能是 null 的東西上呼叫 user.email,TypeScript 會在程式碼執行之前告訴你。
這就是它的全部價值主張。
現在問問自己:在過去 12 個月的 production 事故中,有多少是由 TypeScript 本來可以捕捉到的型別錯誤造成的?把那個數字跟由錯誤商業邏輯、糟糕的資料庫查詢、race condition、缺少的 validation、或基礎設施故障造成的事故數量比較一下。
根據我們的經驗,第一個數字接近於零。第二個數字才是所有痛苦所在。TypeScript 對那些沒有任何幫助。
反對 TypeScript 的技術論據
配置負擔是真實的,而且會持續累積。 每個專案都從 tsconfig.json 開始。然後你需要決定:要不要開 strict mode?要多嚴格?Path alias?Decorator?Module resolution 策略?這些不是一次性的決定——它們會跟著你。第三方套件有時有不相容的假設。Build 工具需要 TypeScript plugin。測試框架需要特殊設定。CI pipeline 需要編譯步驟。每位新加入的工程師都必須先理解你特定的配置,才能做任何有用的事。
我們親自花過好幾個小時除錯與應用程式程式碼完全無關的 TypeScript 配置問題。那些時間完全浪費掉了。
第三方套件的型別定義經常是錯的、過時的,或根本不存在。@types/ 生態系由志願者維護。一個熱門函式庫發布新版本,型別可能落後幾週或幾個月。你最終寫了一堆 @ts-ignore 注釋——到了這個地步,你已經放棄了 TypeScript 本來要給你的東西。或者你花時間為一個沒有型別的套件自己寫型別宣告。兩種結果都不好。
大多數第三方服務和供應商 API 的文件裡沒有 TypeScript 的章節。他們提供 JavaScript 範例。你要自己翻譯。
它在你和你的程式碼之間加了一個編譯步驟。 當 bug 打到 production,你希望能直接檢視實際在跑的東西。用純 JavaScript,你寫的程式碼就是跑的程式碼。用 TypeScript,你看到的是編譯後的輸出。stack trace 引用的是編譯後的行號。瀏覽器 devtools 裡的程式碼不是你寫的那份。這是件小事,直到它不再是小事——在高壓的 production 事故中,任何拖慢診斷速度的東西都是負債。
更快的 build。 TypeScript 編譯需要時間。純 JavaScript 不需要編譯步驟。在規模化後,這對開發者的反饋迴圈和 CI pipeline 成本都有影響。
沒有多餘的層次。 這個 framework 的核心哲學是減少層次,而不是增加。TypeScript 是疊加在 JavaScript 之上的整整一層——一個轉譯步驟、一套型別系統、一個獨立的心智模型。每一層都增加複雜度,每一層都可能出錯。我們要的是更少的層次。
反對 TypeScript 的商業與招聘論據
它縮小了你的招聘人才庫。 JavaScript 工程師到處都是。他們是地球上最大的工程師人才庫。TypeScript 工程師是 JavaScript 工程師的子集——而在這個子集裡,對於 TypeScript 應該怎麼用的意見分歧極大。一個工程師認為「正確」的 TypeScript,在另一個工程師看來是到處都是 any 的爛攤子。你得到的不是標準化的技能,而是在一個本來就更小的人才庫裡的碎片化。
純 JavaScript 意味著:只要你會寫程式,你就能做出貢獻。這就是我們想要的招聘門檻。
它讓你的入職材料翻倍。 如果你用 TypeScript,你的入職流程需要涵蓋 JavaScript 基礎以及 TypeScript 特有的內容。你需要支援 TypeScript 的 linting、TypeScript 特有的坑、TypeScript 的除錯工作流程。對每一個已經很熟悉 TypeScript 的工程師,都有好幾個懂 JavaScript 但不懂 TypeScript 的人。你剛剛幫他們的升溫期加了幾週。
我們把入職速度的目標設在幾天,不是幾週或幾個月。TypeScript 讓這個目標更難達到。
context switching 的成本被低估了。 沒有完全內化 TypeScript 的工程師,最終會不斷切換思維模式——寫程式碼、遇到型別錯誤、除錯型別錯誤、回到實際的程式碼。這種認知負擔在任何單一任務上都是隱形的,但它在每個任務、每一天、每位非 TypeScript 專家工程師身上累積。它讓人變慢,而且難以量化、容易被忽視。
型別安全實際上不是我們面臨的問題。 這個 framework 在每個 API 邊界都使用 Joi 做 runtime validation。從使用者進來的東西,在碰到你的商業邏輯之前都已經被驗證過了。資料庫在 Sequelize model 層有型別定義。TypeScript 型別系統理論上能增加安全性的地方,已經被這個 framework 內建的 validation 模式覆蓋了。你得到了真正的保護(runtime validation),卻不必付出代價(compile-time 型別系統)。
它活不過引入它的那批工程師。 大多數推動 TypeScript 採用的工程師,在公司待的時間不夠長,看不到他們的決定所帶來的完整下游成本。把 TypeScript 加進技術棧的人,通常在新人花三天跟 compiler 搏鬥的時候早就走了。我們看過足夠多次這個模式,已經把它視為一條定律。
AI 時代的論據
我們活在 AI 寫大量 production 程式碼的世界裡。這讓天平更進一步倒向反對 TypeScript。
AI 程式碼生成工具——包括 Claude——對 JavaScript 的了解勝過任何其他語言。它們了解 Express、Sequelize、這個 framework 裡的模式。當程式碼庫一致、文件完善、並使用標準 JavaScript 時,AI 協助的速度快、準確度高、且可靠。
複雜的 TypeScript 型別層級、generic constraint 和 declaration merging 讓 AI 工具感到困惑。它們生成看起來合理但無法通過型別檢查的程式碼。你最終是在除錯 AI 生成的型別錯誤,而不是 AI 生成的邏輯錯誤。TypeScript 的承諾(在 runtime 之前捕捉錯誤)直接與 AI 協助的承諾(快速生成正確程式碼)相衝突——它們並不互相強化。
如果你的目標是最大速度——人類和 AI——純 JavaScript 勝出。
誠實地回應反對意見
「TypeScript 會捕捉真實的 bug。」 有時候。它最可靠地捕捉到的 bug——在 undefined 上呼叫方法、傳錯型別給函式——也能被好的 Joi validation、好的測試和 code review 捕捉到。TypeScript 在這些做法之上的邊際防 bug 價值很小。成本卻不小。
「對大型程式碼庫更好。」 這是 TypeScript 論據最強的版本。在 50 萬行程式碼、50 位工程師的情況下,TypeScript 帶來的 IDE 自動補全和重構工具確實很有價值。我們不是在建一個有 50 萬行程式碼、50 位工程師的程式碼庫。如果你到了那一步,這個 framework 值得演進。不要現在就為一個你還沒有的問題付稅。
「它讓程式碼有了文件。」 好的函式名稱、好的 JSDoc 注釋和清晰的 Joi schema 讓程式碼有了文件。型別注釋是文件的一種形式,不是唯一的形式,也不總是最清晰的形式。這個 framework 強制執行傳達意圖的命名規範和結構,不需要型別系統。
「現在大家都在用 TypeScript 了。」 很多團隊確實如此。很多團隊也同時在放慢腳步、難以招聘、並把工程時間花在型別錯誤上而不是功能上。流行不等於正確。Ruby on Rails 非常流行。PHP 非常流行。我們根據結果做決定,不根據趨勢。
同樣的邏輯適用於這個 framework 中的其他選擇
這裡的思路——減少層次、減少入職時間、擴大招聘人才庫、以團隊速度為優化目標——適用於所有地方:
- 沒有微服務。 一個你能理解的 monolith,勝過一個你搞不懂的分散式系統。微服務是為幾百人的團隊設計的組織解法。如果你有 10 位工程師,你有一個團隊,你應該有一個服務。
- 託管部署(Heroku/Render)優於直接用 AWS。 AWS 強大且複雜。在配置 VPC 和 IAM 政策的工程師,沒有在開發功能。使用託管基礎設施,直到你有特定理由不這樣做為止。
- 只用 POST 和 GET。 消除 REST method 的爭論,從每次 code review 中移除了一整類毫無意義的爭論。action 的名稱承載意義;HTTP method 不需要。
這裡的每一個選擇都是同樣的交換:放棄理論上的彈性,換取實際的速度。
結論
Orbital Express 建立在一個具體的信念上:對一個軟體團隊來說,最重要的指標是它能多快以安全的方式、持續的步調、在團隊隨時間變化的情況下交付功能。
對於這個 framework 所設計服務的那類團隊和產品,TypeScript 並不能改善這個指標。它增加配置、拖慢入職、縮小招聘人才庫、增加編譯層,並引入一類新的錯誤(型別錯誤),卻沒有消除那些真正造成 production 事故的錯誤類別。
我們在 TypeScript 上建過東西。我們在純 JavaScript 上建過東西。建在純 JavaScript 上的產品交付更快、入職時間更短、更容易移交,累積的技術債也更少。
這就是全部的論據。其餘的都是細節。