蒼時弦也
蒼時弦也
資深軟體工程師
發表於

對 Ruby、JavaScript 工程師型別有用嗎?

近期因為 DHH 提到要把 Turbo 的 TypeScript 移除(Turbo 8 is dropping TypeScript)引起不少討論,當天就有人發了合併請求將 TypeScript 全部都拔掉,卻引起不少反彈,後續也有許多不理智的行為,讓 DHH 又發了一篇 Open source hooliganism and the TypeScript meltdown 講這個現象。

在自己約 20 年的程式經驗中,大多是使用動態型別的語言,覺得很適合跟大家聊一聊。

什麼是型別

我不確定是否有一個很好的方式來描述型別的概念,我認為 C 語言的型別上的意義是個不錯的切入點。

在 C 語言裡面,我們會需要去定義資料的結構(Struct)會像這樣撰寫一些定義。

1struct Point {
2  int x;
3  int y;
4}

在使用時,則會需要描述是使用某個資料結構。

1void main() {
2  struct Point p = { .x = 1, .y = 2};
3}

然而每次都要寫 struct Point 並不是那麼方便,因此我們可以用 typedef 關鍵字,設定一個別名,因此就會變成像這樣子的形式。

1typedef struct Point {
2  int x;
3  int y;
4}
5
6void main() {
7  Point p = { .x = 1, .y = 2 };
8}

從我的理解而言,這就構成了最基本的「型別」概念,也就是某種「資料」被我們歸納後,具有相同的特性,就這點而言也可以大概地看出物件的感覺。

從另一個角度來看,在使用靜態型別語言的定義中,這些型別通常可以是某種「類別(Class)」是相當合理的情境。

動態型別

我們對型別有了基本的認識後,就可以進入「動態型別」的探討,也就是 Ruby、JavaScript 這類語言,因為他們無法在「實際運行之前」做到靜態分析(Static Analyze)因此就屬於動態型別。

以 C、Java、Golang 這些語言,在進行「編譯(Compile)」階段時會分析型別的資訊,來判斷運行過程中是否有型別不一致的狀況,因此就能被劃分為靜態型別的語言。然而,這幾年程式語言在這塊的分界變得模糊,像是 TypeScript 讓 JavaScript 具備了這樣的性質,或者 RBS 透過撰寫型別簽章(Signature)利用「註釋(Annotation)」 的方式讓 Ruby 也得以在運行前完成檢查。

正因如此,大多數程式語言,在其底層都是具備型別(Typing)的概念,差異在於運行之前是否能夠知道。

動態型別的語言通常會比靜態型別還慢一些,是因為每一次使用變數時都需要做型別推導的處理,自然會多了一些運算資源消耗。

從這點來看,Turbo 將 TypeScript 移除所造成的影響,應該要不大。同時,Ruby 語言之父 Matz 在 2011 年的一則推文被找出來,裡面提到「要開發優秀的軟體,並不會因為 IDE 或者靜態型別而有所幫助,動態型別的語言從一開始就不提供這樣的幻想」

對許多人來說有「靜態型別」對軟體開發品質仍然會有正面的幫助,我也是同意的。

設計文件

當你接手到一個功能的實作任務時,是否會撰寫(設計)文件呢?設計文件的類型非常多,像是 UML、流程圖,或者進行一場 Event Storming(事件風暴)都能夠產出各種類型的文件,幫助我們對系統加深理解。

除了這些人類閱讀為主的文件外,測試、型別我認為也都可以被視為一種「文件」如同 Ruby 的 RBS 或者 TypeScript 的 .d.ts(Declare 聲明)都可以視為對動態型別的語言,以靜態的方式「註釋」在運行中被期待看到的型態。

簡而言之,對於動態型別還是靜態型別兩種語言的差異,更多在於「半強制寫文件」這件事情上。也因此,在 TypeScript 的使用上常會被笑說用 any 就沒有意義,是非常有道理的。我們如果用 any 表示某個變數的型別,除了等同於沒說之外,程式語言在實際運行時也不會出現這樣的狀況。

實際上也是有在 TypeScript 遇到無法移除掉 any 的狀況,原因是因為使用的套件為了「極度彈性」的相容各種情境,定義了一個複雜到我沒辦法用任何類型滿足的狀況,目前還在努力想辦法改善,因為 ESLint 在建議上是不能有 any 的存在。

假設只是單純的文件問題,實際上也不會讓動態型別和靜態型別的支持者有這麼多討論,因為我們本質上面對是「設計」層面的問題,即使有靜態型別,還是能看到極其糟糕的設計,從 Clean Architecture、Design Pattern 這些書大多用 Java、C# 舉例就能看到,不論有沒有型別,糟糕的設計依舊存在。

假設因為這起事件,你有去嘗試閱讀 Turbo 原始碼過,我相信你會認同 Turbo 的設計確實非常漂亮,DHH 是一位非常優秀的工程師。

看不到的問題

假設型別不是大問題,為什麼大型企業還是以使用 Java、.NET(C#)這類靜態型別的語言為主,我們不常看到動態型別的語言被作為「主要語言」使用呢?

這件事情,就從「設計」問題回歸到「文件」問題上,如果有待過稍具規模的公司,就知道行政流程、文件化會變成非常重要的一環,以一間 3,000 人規模的公司來說,即使流動率只有 5% 來計算,平常就有 150 人在離職、入職,對小公司來說幾乎是重新開一間新公司。

那麼,假設我們希望盡可能的沿用現有的系統跟資源,就需要讓這些不斷新加入的人能最大限度地減少學習成本,透過文件、標準流程會是非常有用的方式。

回過頭看前面 Matz 提到的「優秀的軟體」在推文中的前提是「小團隊跟優秀的工程師」來實現,其實非常符合敏捷開發的想法,即使在敏捷開發中也還是以「功能」或者「產品」為單位,都是認知範圍內的事情。

另一方面,以 DHH 的公司 37signals 規模來看,大致上是個 30 ~ 50 人的規模,是否有型別其實不是那麼關鍵的問題,因此 DHH 的文章提到「This was one project, mostly overseen by one company, that removed TypeScript from their own project,…」一樣,這只是一間公司專案做出的技術決策調整。

Rails 社群一直以來給我的感覺是相當以 DHH 意見為主的,從文章中也可以看出來 DHH 對整個 Rails 生態系的看法應該是更接近「解決公司問題同時開源」的角度,跟許多作為「產品」的開源專案不同,可能跟 React 的情況類似。

就結果來看,對 DHH 以及 37signals 來說是否使用 TypeScript 並不是什麼關鍵的問題,然而對社群來說是否有影響嗎?實際上可能也不大,因為 Turbo 不是什麼極度複雜的專案,如果像 React 那樣複雜,確實會有這樣的需求(當年 Meta 也因此推出了 Flow

最後,Rails 生態系的問題,大概還是落在 DHH 作為整個生態系的意見領袖,看到的問題規模主要是在中小企業,因此像是 Domain-Driven Design 這類議題,一直以來都沒有官方的指南可以參考,大型公司(如:Shopify、GitHub、GitLab)只能不斷自己想辦法解決,然後再貢獻回生態系。

DHH 的決定是「理性合理」的,然而這不符合「趨勢」所以才會有非常多人反對這件事情,也才會被 DHH 説「有多少人真的關心 TypeScript」作為一名專業人員,確實也該要有了解背後脈絡的能力才足夠專業。