---
title: "對 Ruby、JavaScript 工程師型別有用嗎？"
date: 2023-09-13T00:00:00+08:00
publishDate: 2023-09-13T00:00:00+08:00
lastmod: 2023-09-14T12:52:50+08:00
tags: ["Ruby","TypeScript","JavaScript","型別","經驗"]
toc: true
permalink: "https://blog.aotoki.me/posts/2023/09/13/does-ruby-and-javascript-developer-need-static-type/"
language: "zh-tw"
---


近期因為 DHH 提到要把 Turbo 的 TypeScript 移除（[Turbo 8 is dropping TypeScript](https://world.hey.com/dhh/turbo-8-is-dropping-typescript-70165c01)）引起不少討論，當天就有人發了[合併請求](https://github.com/hotwired/turbo/pull/971)將 TypeScript 全部都拔掉，卻引起不少反彈，後續也有許多不理智的行為，讓 DHH 又發了一篇 [Open source hooliganism and the TypeScript meltdown](https://world.hey.com/dhh/open-source-hooliganism-and-the-typescript-meltdown-a474bfda) 講這個現象。

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

<!--more-->

## 什麼是型別{#what-is-type}

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

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

```c
struct Point {
  int x;
  int y;
}
```

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

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

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

```c
typedef struct Point {
  int x;
  int y;
}

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

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

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

## 動態型別{#dynamic-typing}

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

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

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

> 動態型別的語言通常會比靜態型別還慢一些，是因為每一次使用變數時都需要做[型別推導](https://zh.wikipedia.org/zh-tw/%E7%B1%BB%E5%9E%8B%E6%8E%A8%E8%AE%BA)的處理，自然會多了一些運算資源消耗。

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

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

## 設計文件{#design-document}

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

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

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

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

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

> 假設因為這起事件，你有去嘗試閱讀 [Turbo 原始碼](https://github.com/hotwired/turbo)過，我相信你會認同 Turbo 的設計確實非常漂亮，DHH 是一位非常優秀的工程師。

## 看不到的問題{#the-hidden-problem}

假設型別不是大問題，為什麼大型企業還是以使用 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](https://flow.org/)）

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

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

