
四年前,在工作中進行程式碼審查時,我很驚訝 Flow 沒有警告不必要的 null 檢查。上個月,TypeScript 5.6 發布了驗證規則,禁止無用的 nullish 和 truthy 檢查,這揭露了 GitHub 上前 800 個 TypeScript 儲存庫中近 100 個現有錯誤。
這兩個事件是相關聯的,因為四年前程式碼審查的那一刻促使我編寫了 no-constant-binary-expressions
規則,該規則捕捉了各種各樣的錯誤。範例包括
// Expecting empty objects to be falsy
const foo = { ...config } || {};
// Confusing precedence of !
const foo1 = !x == null;
// Confusing ?? or || precedence
const foo2 = x === y ?? true;
反過來說,no-constant-binary-expression
規則也幫助啟發了新加入的 TypeScript 驗證。
考慮到漫長的時間線和許多中間步驟,我認為反思我們是如何走到這一步會很有趣。程式碼審查中的這個觀察是如何像滾雪球一樣,對開發人員產生重大的正面影響?而這個雪球又如何能繼續滾動下去?
為了回答這些問題,回顧事件的時間線會有所幫助。
時間線
- 2020 年 5 月: 我在工作中審查一個 pull request,該請求將一個可為 null 的值改為不可為 null。我注意到作者留下了一個
if
條件,該條件處理了現在不可能發生的值為null
的情況。我開始想知道為什麼 Flow 沒有自動指出這一點。 - 2020 年 5 月: 我在一個內部群組發文,詢問 Flow 團隊關於這個問題。答案是 Flow 和 TypeScript 一樣,都不是健全的。例如,
arr[x]
的類型為不可為 null,但實際上在運行時可能為 undefined。Flow 過去曾實作這些檢查,但它們造成了重大問題,因為它們告訴人們他們的 null 檢查可以安全移除,但實際上並非如此,因此他們移除了這些檢查。Brad Zacher 碰巧看到了這篇文章,並插話說,即使語法方法不如基於類型的方法強大,但語法方法可能是安全的。 - 2020 年 8 月: 我將語法驗證方法實作為一個內部 ESLint 規則,並意識到它可以從 null 檢查推廣到所有常數比較。我在 Meta 的單一儲存庫中運行它,發現它識別出數百個現有錯誤。
- 2020 年 10 月: Brad Zacher 建議我將其作為 ESLint 中的一個新的核心規則提出。我做了,他們 喜歡這個想法。
- 2021 年 11 月 - 2022 年 4 月: 我 為開源重寫了這個規則,由於在 JSX 和樣式等方面的不同立場,這花費了令人驚訝的精力。
- 2022 年 7 月: 我決定撰寫一篇關於新規則的部落格文章。ESLint 團隊當時正在重新設計他們的網站,並尋找更多的部落格內容,所以他們問我是否願意在 官方部落格上發表文章。
- 2023 年 11 月: 這篇部落格文章登上了 Hacker News 的首頁。這篇文章很可能是由某個讀了 我發布的推文 的人分享的,我在推文中分享說,從 ESLint v9.0.0 開始,它將預設在
eslint:recommended
中啟用。 - 2024 年 4 月: 這個規則最終在 ESLint v9.0.0 中預設啟用並發布。這個變更必須等待新的主要版本,因為在
eslint:recommended
中啟用新規則是一個重大變更。 - 2024 年 7 月: TypeScript 團隊為了擴展檢查特定類型常數條件的想法,找到了這篇部落格文章和程式碼。他們擴展了他們執行的驗證集,以包含 ESLint 規則執行的大部分檢查。他們發現了類似的結果。在前 800 個 TS 儲存庫中發現 94 個真實錯誤。ESLint 規則的成功讓他們有信心在
tsc
中預設啟用此檢查。 - 2024 年 9 月: TypeScript 5.6 發布,預設驗證禁止常數 nullish 和 truthy 檢查。
反思
程式碼審查中的這個小觀察能夠促成有意義的生態系統範圍的改進,有許多關鍵因素促成了這一點
- Meta 的內部文化賦予了我這個隨機工程師權力,可以直接與 Flow 團隊的成員進行對話。這次對話發生在一個 Brad Zacher,一位 ESLint 專家,可能會碰巧看到並插話的場合。
- Meta 的單一儲存庫讓我可以直接訪問龐大的程式碼庫,這讓我能夠輕鬆運行規則的早期草稿,以評估驗證對大量真實程式碼的影響。
- Meta 的自主文化給予我自由,讓我能夠承擔編寫這個規則的額外任務,儘管這並非我團隊的職責。
- ESLint 的可插拔架構賦予我權力,讓我能夠編寫自己的規則,並輕鬆地將其部署到整個公司,而無需說服任何把關者。
- ESLint 團隊對新貢獻者添加新的核心規則的開放態度和積極支持,儘管 2020 年的政策 規定只接受與新語言功能相關的新規則。
- 主動溝通由我發起並由 ESLint 團隊放大的工作,形式包括部落格文章和推文。這些讓 TypeScript 團隊能夠將他們收到的關於禁止
if (/regex/)
的更具體請求,與檢測常數條件的更廣泛想法聯繫起來。 - 不滿足於僅僅指出程式碼審查中無用的 null 檢查,因為我懷疑可能存在針對這類問題的根本解決方案。由於有點執著,我並不滿足於僅僅為我自己、我的團隊或我的公司啟用該解決方案,而是希望為更廣泛的生態系統啟用它。
下一步是什麼
Brad 最初在內部貼文中的觀察產生漣漪,歷時四年才達到現在這個地步,但我認為這裡的想法有可能產生更深遠的共鳴
- TypeScript 和 Flow 可以在內部追蹤他們碰巧知道是健全的類型,並根據這些資料機會性地報告錯誤,從而允許在更多情況下執行此類檢查。
- 其他先前由於與 Flow 相同的原因而避免報告不必要檢查的不健全語言,可以使用相同的方法來捕捉邏輯錯誤。
- 更廣泛地說,死碼消除是編譯器設計中一個眾所周知的領域,有許多編纂的技術和方法。然而,它們幾乎總是作為編譯器後端的最佳化應用。我懷疑許多相同的死碼消除技術可以被帶到編譯器的前端,並用於檢測和報告錯誤。
結論
這項努力跨越了多年和多個組織。
作為 Meta 這家營利性公司內積極主動的個人,我能夠發起一場對話,從中產生了一個想法。然後,我能夠利用 ESLint 在內部驗證這個想法。一旦驗證,ESLint 專案這個非營利組織,透過其廣泛的採用、推薦的規則集和部落格,將這個想法帶給了廣大的受眾。隨著這個想法得到記錄和大規模驗證,Microsoft 這家另一家營利性公司的工程師,能夠透過他們的開源專案 TypeScript 將驗證帶給更廣大的受眾。
每個組織都根據自身的優勢和定位,發揮了不同但關鍵的作用。我相信貫穿整個過程的一個共同主軸,促成了如此規模的協作,就是想法的主動社會化。從在程式碼審查期間提出問題,到向當地專家請教,再到在公開部落格文章和推文中分享想法,每一個擴大的社會化圈子都改進了這個想法,並最終幫助它到達了今天所觸及的廣大受眾。
感謝 Brad Zacher 的最初關鍵觀察和持續鼓勵,感謝 Nicholas C. Zakas 和 Milos Djermanovic 在程式碼審查期間對規則做出的重大貢獻,以及感謝 Ryan Cavanaugh 將這些相同類型的驗證帶到 TypeScript 生態系統。