編程語言按照類型檢查可以分為兩大類:靜態(tài)類型 (Static Typing) 和 動態(tài)類型 (Dynamic Typing)砍聊。在現(xiàn)在比較流行的這些語言里页藻,靜態(tài)類型的代表人物有 Java桨嫁、C/C++、Golang 等份帐,動態(tài)類型有 Python璃吧、Ruby 等。
靜態(tài)類型和動態(tài)類型有什么區(qū)別呢废境?為什么在程序語言設(shè)計(jì)時要考慮是靜態(tài)還是動態(tài)畜挨?在寫代碼時筒繁,Python 寫起來簡潔,效率高巴元,可能100行 Java 的程序10行 Python 就能搞定毡咏,所以以前我覺得靜態(tài)類型的語言有點(diǎn)太死板了,不如動態(tài)類型的逮刨。但是經(jīng)過這段時間編程語言的學(xué)習(xí)呕缭,不能說一個比另一個好,個有所長吧修己。這篇文章從3個角度恢总、6個方面對比靜態(tài)類型和動態(tài)類型。
為什么會有靜態(tài)類型/動態(tài)類型的概念睬愤?
程序都需要進(jìn)行錯誤的檢查片仿。比如 3 / 0,這個程序會有錯誤戴涝,我們應(yīng)該在什么時候進(jìn)行檢查呢滋戳?
- 在寫程序時钻蔑,只要編輯器里出現(xiàn)除以0啥刻,報(bào)錯。
- 在編譯時咪笑,如果檢查到了除以0可帽,報(bào)錯。
- 在程序運(yùn)行時窗怒,運(yùn)行到了除以9映跟,報(bào)錯。
- 不報(bào)錯扬虚,返回?zé)o窮大 (+inf.0)努隙。
不同的語言設(shè)計(jì)上會選擇在這4個過程中不同時候去報(bào)錯。靜態(tài)類型和動態(tài)類型的區(qū)別在于什么時候報(bào)類型的錯誤辜昵?荸镊,比如說 3 / "a“,靜態(tài)類型多是在編譯時堪置,動態(tài)類型多是在程序運(yùn)行時躬存。怎么報(bào)類型的錯誤呢?語言里會有類型檢查的機(jī)制舀锨,類型檢查的目的是避免程序發(fā)生一些事情岭洲。
編程語言在設(shè)計(jì)時,要考慮 什么程序要類型檢查坎匿?盾剩,怎么執(zhí)行類型檢查雷激?。靜態(tài)類型和動態(tài)類型是這兩個問題不同回答的產(chǎn)物告私。
從寫代碼的角度對比
方便性 Convenience
靜態(tài)類型更好:靜態(tài)類型比較方便侥锦,因?yàn)椴挥萌z查 x 是不是 number,* 默認(rèn)只能是 number德挣。
; Racket
(define (cube x)
(if (not (number? x))
(error "bad arguments")
(* x x x)))
(* ML *)
fun cube x = x * x * x
動態(tài)類型更好:動態(tài)類型比較方便恭垦,因?yàn)橐粋€函數(shù)可以根據(jù)需要返回不同的類型。靜態(tài)類型卻需要去構(gòu)造一個新的數(shù)據(jù)類型才能實(shí)現(xiàn)格嗅。
; Racket
(define (f y) (if (> y 0) (+ y y) "hi"))
(* ML *)
datatype t = Int of int | String of string
fun f y = if y > 0 then Int(y+y) else String "hi"
fun foo x = case f x of
Int i => Int.toString i
| String s => s
更早的發(fā)現(xiàn)錯誤 Catching bugs earlier
靜態(tài)類型在編譯時就能發(fā)現(xiàn)類型上的錯誤番挺,都不用寫 tests,可以比動態(tài)類型更早的找到 bug屯掖。
但是喜歡動態(tài)類型的人會說玄柏,靜態(tài)類型只能找到"簡單"的錯誤,還是需要寫單元測試的贴铜,在寫單元測試時粪摘,肯定就能發(fā)現(xiàn)這些"簡單"的錯誤了。
性能 Performance
靜態(tài)類型的程序在運(yùn)行時更快绍坝,因?yàn)樵诰幾g時已經(jīng)進(jìn)行了檢測徘意,不需要去儲存和檢測類型,可以節(jié)省程序運(yùn)行的時間和空間轩褐。
但是喜歡動態(tài)類型的人會說椎咧,動態(tài)類型在性能很關(guān)鍵的部分,可以有一些辦法去優(yōu)化類型的儲存和檢測把介,比如說 (let ([x (+ y y)]) (* x 4))
勤讽,有兩個 y,可以只檢測一個拗踢,4 是一個整數(shù)脚牍,不需要檢測,x 是 y + y 的結(jié)果巢墅,所以后面的那個 x 也可以不用檢測诸狭。通過這些方式可以對程序進(jìn)行一些優(yōu)化,而不需要像靜態(tài)類型一樣要受到各種類型的限制砂缩。
代碼重用 Code Reuse
動態(tài)類型更好:動態(tài)類型代碼重用率更高作谚,因?yàn)闆]有嚴(yán)格的類型系統(tǒng),代碼可以被不同類型的數(shù)據(jù)重用庵芭。一個最簡單的例子
# Ruby
def double x
x + x
end
x 可以是數(shù)字妹懒,把數(shù)字翻倍。x 可以是 string双吆,把兩個 string 連在一起眨唬。
動態(tài)類型中一個 list 里可以有不同的類型的數(shù)據(jù)会前,在循環(huán)遍歷時會,能重用更多代碼匾竿。
靜態(tài)類型更好:靜態(tài)類型也有代碼重用的很多方法瓦宜,比如泛型,子類型等等岭妖。而且一個 list 只有一種類型的數(shù)據(jù)临庇,可以避免一些難找的bug,也可以避免因?yàn)轭愋妥杂啥鵀E用一些庫昵慌。
原型開發(fā) Prototyping
動態(tài)類型更好:動態(tài)類型更適合原型開發(fā)假夺,因?yàn)樵?Prototyping 時,不一定知道確切的數(shù)據(jù)結(jié)構(gòu)和函數(shù)斋攀,類型上的自由可以不用在做原型時就確定數(shù)據(jù)結(jié)構(gòu)已卷,導(dǎo)致了要一直不斷的去滿足類型的檢查,降低了原型開發(fā)的效率淳蔼。
靜態(tài)類型更好:雖然效率上不一定比的上動態(tài)類型侧蘸,靜態(tài)類型能更好的記錄整個系統(tǒng)在 Prototyping 的過程中,可以知道整個過程數(shù)據(jù)類型鹉梨、數(shù)據(jù)結(jié)構(gòu)是怎么變化的讳癌。如果是跟之前完全不相關(guān)的代碼,可以直接重寫俯画,不需要在之前的做更改析桥。在之前代碼不確定的地方,可以用一些表示所有其他類型的方法艰垂,比如 | _ => raise Unimplemented
。
再開發(fā)和維護(hù) Evolution & Maintaince
動態(tài)類型更好:在更改代碼時埋虹,可以把代碼能接受的變得更"寬"猜憎,比如說修改函數(shù)返回類型,調(diào)用的代碼如果對返回類型沒有問題搔课,可以不用更改胰柑。靜態(tài)類型必須要更改所有調(diào)用的代碼,不能進(jìn)行局部的測試爬泥。
靜態(tài)類型更好:動態(tài)類型的不用更改舊代碼是一個隱患柬讨,坊間流傳動態(tài)類型是「寫時一時爽,重構(gòu)火葬場」袍啡,靜態(tài)類型的類型檢查會列出所有需要更改的地方踩官,可以避免一些隱藏的bug。
總結(jié)
經(jīng)過這些對比境输,可以看出靜態(tài)類型和動態(tài)類型各有所長蔗牡,不能簡單粗暴的說一種比另外一種更好颖系。個人而言,我覺得動態(tài)類型更適合比較小的程序辩越,像 Python嘁扼,Ruby,做為腳本語言黔攒,能簡單快速的寫完對文件的處理等趁啸。動態(tài)類型 Java 和 C++ 則能過支持大型的軟件工程項(xiàng)目。
當(dāng)然具體選擇靜態(tài)類型或者動態(tài)類型督惰,取決于想要什么時候做類型檢查莲绰?想要什么樣的語言特性,并且應(yīng)該知道選擇的 trade-off 是什么樣的姑丑。
Reference
Coursera Programming Languages, Part B Week 3