静态类型和动态类型有什么区别?

编程语言按照类型检查可以分为两大类:静态类型 (Static Typing)动态类型 (Dynamic Typing)。在现在比较流行的这些语言里,静态类型的代表人物有 Java、C/C++、Golang 等,动态类型有 Python、Ruby 等。

静态类型和动态类型有什么区别呢?为什么在程序语言设计时要考虑是静态还是动态?在写代码时,Python 写起来简洁,效率高,可能100行 Java 的程序10行 Python 就能搞定,所以以前我觉得静态类型的语言有点太死板了,不如动态类型的。但是经过这段时间编程语言的学习,不能说一个比另一个好,个有所长吧。这篇文章从3个角度、6个方面对比静态类型和动态类型。

为什么会有静态类型/动态类型的概念?

程序都需要进行错误的检查。比如 3 / 0,这个程序会有错误,我们应该在什么时候进行检查呢?

  1. 在写程序时,只要编辑器里出现除以0,报错。
  2. 在编译时,如果检查到了除以0,报错。
  3. 在程序运行时,运行到了除以9,报错。
  4. 不报错,返回无穷大 (+inf.0)。

不同的语言设计上会选择在这4个过程中不同时候去报错。静态类型和动态类型的区别在于什么时候报类型的错误?,比如说 3 / “a“,静态类型多是在编译时,动态类型多是在程序运行时。怎么报类型的错误呢?语言里会有类型检查的机制,类型检查的目的是避免程序发生一些事情

编程语言在设计时,要考虑 什么程序要类型检查?怎么执行类型检查?。静态类型和动态类型是这两个问题不同回答的产物。

从写代码的角度对比

方便性 Convenience

静态类型更好:静态类型比较方便,因为不用去检查 x 是不是 number,* 默认只能是 number。

; Racket
(define (cube x)
(if (not (number? x))
(error "bad arguments")
(* x x x)))

(* ML *)
fun cube x = x * x * x

动态类型更好:动态类型比较方便,因为一个函数可以根据需要返回不同的类型。静态类型却需要去构造一个新的数据类型才能实现。

; 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

更早的发现错误 Catching bugs earlier

静态类型在编译时就能发现类型上的错误,都不用写 tests,可以比动态类型更早的找到 bug。

但是喜欢动态类型的人会说,静态类型只能找到”简单”的错误,还是需要写单元测试的,在写单元测试时,肯定就能发现这些”简单”的错误了。

性能 Performance

静态类型的程序在运行时更快,因为在编译时已经进行了检测,不需要去储存和检测类型,可以节省程序运行的时间和空间。

但是喜欢动态类型的人会说,动态类型在性能很关键的部分,可以有一些办法去优化类型的储存和检测,比如说 (let ([x (+ y y)]) (* x 4)),有两个 y,可以只检测一个,4 是一个整数,不需要检测,x 是 y + y 的结果,所以后面的那个 x 也可以不用检测。通过这些方式可以对程序进行一些优化,而不需要像静态类型一样要受到各种类型的限制。

代码重用 Code Reuse

动态类型更好:动态类型代码重用率更高,因为没有严格的类型系统,代码可以被不同类型的数据重用。一个最简单的例子

# Ruby
def double x
x + x
end

x 可以是数字,把数字翻倍。x 可以是 string,把两个 string 连在一起。

动态类型中一个 list 里可以有不同的类型的数据,在循环遍历时会,能重用更多代码。

静态类型更好:静态类型也有代码重用的很多方法,比如泛型,子类型等等。而且一个 list 只有一种类型的数据,可以避免一些难找的bug,也可以避免因为类型自由而滥用一些库。

原型开发 Prototyping

动态类型更好:动态类型更适合原型开发,因为在 Prototyping 时,不一定知道确切的数据结构和函数,类型上的自由可以不用在做原型时就确定数据结构,导致了要一直不断的去满足类型的检查,降低了原型开发的效率。

静态类型更好:虽然效率上不一定比的上动态类型,静态类型能更好的记录整个系统在 Prototyping 的过程中,可以知道整个过程数据类型、数据结构是怎么变化的。如果是跟之前完全不相关的代码,可以直接重写,不需要在之前的做更改。在之前代码不确定的地方,可以用一些表示所有其他类型的方法,比如 | _ => raise Unimplemented

再开发和维护 Evolution & Maintaince

动态类型更好:在更改代码时,可以把代码能接受的变得更”宽”,比如说修改函数返回类型,调用的代码如果对返回类型没有问题,可以不用更改。静态类型必须要更改所有调用的代码,不能进行局部的测试。

静态类型更好:动态类型的不用更改旧代码是一个隐患,坊间流传动态类型是「写时一时爽,重构火葬场」,静态类型的类型检查会列出所有需要更改的地方,可以避免一些隐藏的bug。

总结

经过这些对比,可以看出静态类型和动态类型各有所长,不能简单粗暴的说一种比另外一种更好。个人而言,我觉得动态类型更适合比较小的程序,像 Python,Ruby,做为脚本语言,能简单快速的写完对文件的处理等。动态类型 Java 和 C++ 则能过支持大型的软件工程项目。

当然具体选择静态类型或者动态类型,取决于想要什么时候做类型检查?想要什么样的语言特性,并且应该知道选择的 trade-off 是什么样的。

Reference

Coursera Programming Languages, Part B Week 3