靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型有什么區(qū)別?

編程語(yǔ)言按照類(lèi)型檢查可以分為兩大類(lèi):靜態(tài)類(lèi)型 (Static Typing)動(dòng)態(tài)類(lèi)型 (Dynamic Typing)。在現(xiàn)在比較流行的這些語(yǔ)言里,靜態(tài)類(lèi)型的代表人物有 Java、C/C++、Golang 等,動(dòng)態(tài)類(lèi)型有 Python、Ruby 等。

靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型有什么區(qū)別呢?為什么在程序語(yǔ)言設(shè)計(jì)時(shí)要考慮是靜態(tài)還是動(dòng)態(tài)?在寫(xiě)代碼時(shí),Python 寫(xiě)起來(lái)簡(jiǎn)潔,效率高,可能100行 Java 的程序10行 Python 就能搞定,所以以前我覺(jué)得靜態(tài)類(lèi)型的語(yǔ)言有點(diǎn)太死板了,不如動(dòng)態(tài)類(lèi)型的。但是經(jīng)過(guò)這段時(shí)間編程語(yǔ)言的學(xué)習(xí),不能說(shuō)一個(gè)比另一個(gè)好,個(gè)有所長(zhǎng)吧。這篇文章從3個(gè)角度、6個(gè)方面對(duì)比靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型。

為什么會(huì)有靜態(tài)類(lèi)型/動(dòng)態(tài)類(lèi)型的概念?

程序都需要進(jìn)行錯(cuò)誤的檢查。比如 3 / 0,這個(gè)程序會(huì)有錯(cuò)誤,我們應(yīng)該在什么時(shí)候進(jìn)行檢查呢?

  1. 在寫(xiě)程序時(shí),只要編輯器里出現(xiàn)除以0,報(bào)錯(cuò)。
  2. 在編譯時(shí),如果檢查到了除以0,報(bào)錯(cuò)。
  3. 在程序運(yùn)行時(shí),運(yùn)行到了除以9,報(bào)錯(cuò)。
  4. 不報(bào)錯(cuò),返回?zé)o窮大 (+inf.0)。

不同的語(yǔ)言設(shè)計(jì)上會(huì)選擇在這4個(gè)過(guò)程中不同時(shí)候去報(bào)錯(cuò)。靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型的區(qū)別在于什么時(shí)候報(bào)類(lèi)型的錯(cuò)誤?,比如說(shuō) 3 / "a“,靜態(tài)類(lèi)型多是在編譯時(shí),動(dòng)態(tài)類(lèi)型多是在程序運(yùn)行時(shí)。怎么報(bào)類(lèi)型的錯(cuò)誤呢?語(yǔ)言里會(huì)有類(lèi)型檢查的機(jī)制,類(lèi)型檢查的目的是避免程序發(fā)生一些事情

編程語(yǔ)言在設(shè)計(jì)時(shí),要考慮 什么程序要類(lèi)型檢查?,怎么執(zhí)行類(lèi)型檢查?。靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型是這兩個(gè)問(wèn)題不同回答的產(chǎn)物。

從寫(xiě)代碼的角度對(duì)比

方便性 Convenience

靜態(tài)類(lèi)型更好:靜態(tài)類(lè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

動(dòng)態(tài)類(lèi)型更好:動(dòng)態(tài)類(lèi)型比較方便,因?yàn)橐粋€(gè)函數(shù)可以根據(jù)需要返回不同的類(lèi)型。靜態(tài)類(lèi)型卻需要去構(gòu)造一個(gè)新的數(shù)據(jù)類(lèi)型才能實(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)錯(cuò)誤 Catching bugs earlier

靜態(tài)類(lèi)型在編譯時(shí)就能發(fā)現(xiàn)類(lèi)型上的錯(cuò)誤,都不用寫(xiě) tests,可以比動(dòng)態(tài)類(lèi)型更早的找到 bug。

但是喜歡動(dòng)態(tài)類(lèi)型的人會(huì)說(shuō),靜態(tài)類(lèi)型只能找到"簡(jiǎn)單"的錯(cuò)誤,還是需要寫(xiě)單元測(cè)試的,在寫(xiě)單元測(cè)試時(shí),肯定就能發(fā)現(xiàn)這些"簡(jiǎn)單"的錯(cuò)誤了。

性能 Performance

靜態(tài)類(lèi)型的程序在運(yùn)行時(shí)更快,因?yàn)樵诰幾g時(shí)已經(jīng)進(jìn)行了檢測(cè),不需要去儲(chǔ)存和檢測(cè)類(lèi)型,可以節(jié)省程序運(yùn)行的時(shí)間和空間。

但是喜歡動(dòng)態(tài)類(lèi)型的人會(huì)說(shuō),動(dòng)態(tài)類(lèi)型在性能很關(guān)鍵的部分,可以有一些辦法去優(yōu)化類(lèi)型的儲(chǔ)存和檢測(cè),比如說(shuō) (let ([x (+ y y)]) (* x 4)),有兩個(gè) y,可以只檢測(cè)一個(gè),4 是一個(gè)整數(shù),不需要檢測(cè),x 是 y + y 的結(jié)果,所以后面的那個(gè) x 也可以不用檢測(cè)。通過(guò)這些方式可以對(duì)程序進(jìn)行一些優(yōu)化,而不需要像靜態(tài)類(lèi)型一樣要受到各種類(lèi)型的限制。

代碼重用 Code Reuse

動(dòng)態(tài)類(lèi)型更好:動(dòng)態(tài)類(lèi)型代碼重用率更高,因?yàn)闆](méi)有嚴(yán)格的類(lèi)型系統(tǒng),代碼可以被不同類(lèi)型的數(shù)據(jù)重用。一個(gè)最簡(jiǎn)單的例子

# Ruby
def double x
    x + x
end

x 可以是數(shù)字,把數(shù)字翻倍。x 可以是 string,把兩個(gè) string 連在一起。

動(dòng)態(tài)類(lèi)型中一個(gè) list 里可以有不同的類(lèi)型的數(shù)據(jù),在循環(huán)遍歷時(shí)會(huì),能重用更多代碼。

靜態(tài)類(lèi)型更好:靜態(tài)類(lèi)型也有代碼重用的很多方法,比如泛型,子類(lèi)型等等。而且一個(gè) list 只有一種類(lèi)型的數(shù)據(jù),可以避免一些難找的bug,也可以避免因?yàn)轭?lèi)型自由而濫用一些庫(kù)。

原型開(kāi)發(fā) Prototyping

動(dòng)態(tài)類(lèi)型更好:動(dòng)態(tài)類(lèi)型更適合原型開(kāi)發(fā),因?yàn)樵?Prototyping 時(shí),不一定知道確切的數(shù)據(jù)結(jié)構(gòu)和函數(shù),類(lèi)型上的自由可以不用在做原型時(shí)就確定數(shù)據(jù)結(jié)構(gòu),導(dǎo)致了要一直不斷的去滿(mǎn)足類(lèi)型的檢查,降低了原型開(kāi)發(fā)的效率。

靜態(tài)類(lèi)型更好:雖然效率上不一定比的上動(dòng)態(tài)類(lèi)型,靜態(tài)類(lèi)型能更好的記錄整個(gè)系統(tǒng)在 Prototyping 的過(guò)程中,可以知道整個(gè)過(guò)程數(shù)據(jù)類(lèi)型、數(shù)據(jù)結(jié)構(gòu)是怎么變化的。如果是跟之前完全不相關(guān)的代碼,可以直接重寫(xiě),不需要在之前的做更改。在之前代碼不確定的地方,可以用一些表示所有其他類(lèi)型的方法,比如 | _ => raise Unimplemented。

再開(kāi)發(fā)和維護(hù) Evolution & Maintaince

動(dòng)態(tài)類(lèi)型更好:在更改代碼時(shí),可以把代碼能接受的變得更"寬",比如說(shuō)修改函數(shù)返回類(lèi)型,調(diào)用的代碼如果對(duì)返回類(lèi)型沒(méi)有問(wèn)題,可以不用更改。靜態(tài)類(lèi)型必須要更改所有調(diào)用的代碼,不能進(jìn)行局部的測(cè)試。

靜態(tài)類(lèi)型更好:動(dòng)態(tài)類(lèi)型的不用更改舊代碼是一個(gè)隱患,坊間流傳動(dòng)態(tài)類(lèi)型是「寫(xiě)時(shí)一時(shí)爽,重構(gòu)火葬場(chǎng)」,靜態(tài)類(lèi)型的類(lèi)型檢查會(huì)列出所有需要更改的地方,可以避免一些隱藏的bug。

總結(jié)

經(jīng)過(guò)這些對(duì)比,可以看出靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型各有所長(zhǎng),不能簡(jiǎn)單粗暴的說(shuō)一種比另外一種更好。個(gè)人而言,我覺(jué)得動(dòng)態(tài)類(lèi)型更適合比較小的程序,像 Python,Ruby,做為腳本語(yǔ)言,能簡(jiǎn)單快速的寫(xiě)完對(duì)文件的處理等。動(dòng)態(tài)類(lèi)型 Java 和 C++ 則能過(guò)支持大型的軟件工程項(xiàng)目。

當(dāng)然具體選擇靜態(tài)類(lèi)型或者動(dòng)態(tài)類(lèi)型,取決于想要什么時(shí)候做類(lèi)型檢查?想要什么樣的語(yǔ)言特性,并且應(yīng)該知道選擇的 trade-off 是什么樣的。

Reference

Coursera Programming Languages, Part B Week 3

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容