「Python 比 Java 簡單」這句話的一半是對的——Python 比 Java 簡單開始

# Python:直接寫
def add(a, b):
    return a + b
// Java:需要型別宣告
public static int add(int a, int b) {
    return a + b;
}

Python 的版本更短,更直觀,初學者三分鐘就能跑起來。

但如果你的 codebase 長到 10 萬行,Python 的 add(a, b) 裡的 ab 是什麼——整數?浮點數?複數?字串?你要讀函式實作或呼叫端才知道。

這個問題的規模,隨著 codebase 成長而放大。


靜態型別做了什麼

靜態型別系統:型別在編譯時(或在你寫 code 時,透過型別推斷)確定。如果你用一個 int 的地方放了 string,編譯器報錯,你的程式碼跑不起來。

function add(a: number, b: number): number {
    return a + b;
}
 
add(1, "hello");  // 編譯錯誤,不需要執行就知道錯了

靜態型別帶來的好處是:你在 IDE 裡就看到紅線,不需要跑到 production 才發現(編譯時錯誤 < 執行時錯誤);autocomplete、go-to-definition、refactoring 都依賴型別資訊,IDE 支援更強;函式簽名本身就是文件,說明「這個函式要什麼、給你什麼」。


動態型別的合理性

動態型別系統:型別在執行時確定。同一個變數可以在不同時間點是不同型別。

x = 5        # x 是 int
x = "hello"  # x 現在是 str,沒問題
x = [1,2,3]  # x 現在是 list,還是沒問題

動態型別的優點在快速迭代場景:

  • 寫 prototype 或腳本,不需要宣告型別就能跑
  • Duck typing 讓程式碼有自然的彈性:函式接受「任何有 .read() 方法的物件」,不管它是 File 還是 Socket 還是 StringIO

1990 年代的 web 開發,大多是小型單體應用,動態型別的快速迭代優勢是真實的。


型別推斷:不需要在靜態和動態之間選邊

現代靜態型別語言大量依賴型別推斷——你不需要到處寫型別宣告,編譯器自己推:

// Go:型別推斷
x := 5             // 推斷為 int
names := []string{"Alice", "Bob"}  // 推斷為 []string
 
// 函式回傳型別推斷
func double(x int) int {  // 這裡還是需要宣告參數型別
    return x * 2
}
// TypeScript:廣泛的推斷
const x = 5;  // 推斷為 number
const arr = [1, 2, 3];  // 推斷為 number[]
const doubled = arr.map(n => n * 2);  // 推斷為 number[]

Rust 的型別推斷更強大,在某些情況下連函式參數都可以推斷(在 closure 裡)。

型別推斷讓靜態型別的寫法簡潔度接近動態型別,但保留了編譯時檢查的好處。


漸進式型別:最佳妥協?

動態語言加入可選的型別標注:

# Python type hints(可選,不影響 runtime)
def add(a: int, b: int) -> int:
    return a + b
 
def process(items: list[str]) -> None:
    for item in items:
        print(item.upper())

你可以在關鍵路徑加型別標注,用 mypy/pyright 做靜態分析,不加的地方繼續動態。

好處:不需要一次性重寫所有程式碼,可以逐步引入。
壞處:沒有型別標注的程式碼還是動態的,保護力不完整;型別標注是可選的,在 code review 沒有嚴格要求的情況下容易被省略。

TypeScript 對 JavaScript 也是這個思路——但 TypeScript 的覆蓋率通常比 Python type hints 更高,因為 TypeScript 的型別工具整合進 IDE 的體驗更好。


何時動態型別真的夠用

  • 腳本和自動化工具:一次性的腳本,錯誤了重跑就好,靜態型別的收益不值得投入
  • ML/資料分析:Python 在這個領域的生態系無法替代,動態型別是代價,NumPy/Pandas 的使用方式本來就是探索性的
  • 小型單體服務:服務小、team 小、週期短,靜態型別的基礎建設(type stubs、CI 型別檢查)的投入可能大於收益

何時靜態型別是必要的

  • 大型 codebase(> 50k 行):沒有型別資訊,你不知道任何一個函式的輸入期望是什麼
  • 多人長期維護:六個月後讀別人寫的函式,型別簽名是最快的文件
  • 公開 API:你的 SDK 如果沒有型別,呼叫方用起來效率極差

實際後端開發的策略

對於主要用動態語言的 team:

  • 新寫的程式碼要有型別標注(Python type hints / TypeScript strict mode)
  • mypy/pyright/tsc --noEmit 加進 CI,讓型別錯誤和測試失敗一樣會擋住 merge
  • 改動既有程式碼時順手補型別(童子軍原則)

對於靜態語言 team:

  • 不要讓型別系統變成 any/interface{}/Object 的堆砌——那是靜態型別的形式,動態型別的本質
  • 善用型別系統表達業務規則(OrderID vs UserID 都是 int,但型別系統能防止你把 order ID 傳進 user 查詢)

下一篇:Structural vs Nominal Typing