接之前一篇 Pattern Matching 的文章,Type System 是另一项编程语言,或者说编译器所提供的便利。Pattern Matching 可以让我们少写代码,而 Type System 可以让我们少犯错误,减少 Type 相关的各种 bug。 一般来说,我们写代码时为了降低 bug 率,一是依赖于程序员自身的经验积累,二是靠编译器做各种静态检查,type system 则是属于静态检查这一类。Swift 较之 Objective C 的 type system 有了很大的改进,下面文章中主要是介绍 Swift 相关的一些特性。在开始之前,先聊下如何靠程序员经验来降低 bug。 Bug 第六感从我自身的体验推断,我相信大部分程序员在写代码的时候,对于代码是否存在 bug 是有一定感知的。只不过有些新入行的朋友,在写代码的时候操之过急,或者由于和产品经理讨论吃了败仗心情不佳,coding 时目标变成了写能 work 的代码,而不是写高质量的代码。 facebook 面试有一个环节叫 whiteboard coding,要求程序员能在白板上写出几乎是 「bug free」的代码,这听起来有点耸人听闻,写代码没有 Xcode 提示就罢了,bug free 更是难上加难了。写一段几乎没有 bug 的代码到底有多难呢?说难不难,说易不易。 写代码时,慢一点,再慢一点。好好的想下代码有可能出错的地方在哪,想清楚了 bug 就少。一般来说,要减少 bug 量,一是靠程序员自身修养,二是靠编译器提供的静态检查。Type System 属于第二类,在深入之前,先简单聊下如何靠自身修养降低 bug 率,提升 bug 感知的第六感。 少写 Bug 的简易准则要提升程序员的自身修养来降低 bug 率,是个大话题,而且多和自身的知识积累有关,需要长年累月的学习和养成。本文的目的不在于此,所以只介绍一个小技巧来养成感知 bug 的好习惯。 我们可以粗略的将我们所写代码分为 data 和 behavior,behavior 围绕 data 执行各种逻辑。一个函数可以看做是一个 behavior,而函数本身又由若干 data 和 behavior 所构成。很多时候,代码有 bug,是因为 data 出现了预料之外的变化。有一个简易准则可以减少这类 bug:只要遇到 data,就做 aggressive check。 具体到一个自定义的函数,函数会包含哪些 data 呢?细心理一理没几个。
这几类 data 是我们在一个函数中最经常遇到的,只要我们对他们做好检查就可保平安。做哪些检查呢?最常见的也就那么几样,比如是否为 0,为 nil,数组元素 count 为 0,如果期待正数则是否为负数,数组是否越界,多线程是否安全等。做下总结就可以完成大部分的可靠性检查。简而言之,只要是使用 data 的时候,就围绕 data 做好应该的检查,做到这点,写一个几乎没有 bug 的函数就不怎么难了。 这个原则更精准的表达是:在任何场景下,无论是定义变量还是使用变量,都对变量的各种可能性做检查和保护。 Type System回到我们的正题 Type System,Type System 是由编程语言和 type 相关的各种规则所构成。它的用处也简单,可以帮助我们减少和 type 相关的 bug。 编程语言大多都有自己的 Type System,Objective C 和 Swift 都有。在开始讨论 Type System 之前,要明确 Type 的定义。 Type 就像自然语言里的名词,动词,介词等等,可以规范我们的表达。在编程语言中,type 则是一种避免代码表达错误的约束。Type 不仅仅包括诸如 int,float,bool 这类 primitive type,对象的 class type,还包括 function,block 等不那么明显的 type。变量,常量,函数等等都(且一定)具备 type 信息,有些一眼能看出,有些要靠推断。 Static vs Dynamic有些 type 信息是交由程序员去推断和维护的,有些则是留给编译器去管理的。前者的 type 约束是在 runtime 检查的,偏向「dynamic」,后者则在 compile 的时候就做了 check,偏向「static」。 很多技术文章都会讨论编程语言的 dynamic 和 static 属性,我们要分清楚 dynamic 和 static 其实是个宽泛的说法,他们可能包含不同的语义和场景。dynamic 和 static 既可以用来讨论 type system,又可以用来形容函数调用机制。比如我们认为 Swift 是 statically typed,但 Objective C 的 runtime 和 message 机制又显然是 dynamic 的,这两种场景下 static 和 dynamic 说的其实不是一回事。 回到 type system 的场景,讨论下语言是 statically typed 还是 dynamically typed。还是要进一步看场景,在 Objective C 中,type 信息既可以是 static 的,也可以是 dynamic 的,看我们如何使用了,比如下面的代码中 type 信息是 static 的:
因为 type 的上下文信息是完整的,编译器可以做类型判断。而如下代码中 type 信息则是 dynamic 的:
由于 id 可以指向任意对象类型,id 可以在不同的时间点里指向不同的类型,编译器此时无法根据类型信息作出判断,是否存在类型使用错误的。所以我们会说像 Objective C 这类编程语言在 type system 上,是同时具备 static 和 dynamic 属性的,关键还是看具体的使用场景。 但 Swift 却是货真价实的,纯粹的 statically typed 编程语言,不具备任何 dynamically typed 的属性。比如在 Swift 中,如下代码是无法通过编译的:
编译器会提示:Type annotation missing in pattern,也就是缺少 type 信息。要声明一个变量,我们可以通过如下两种方式来提供 type 信息:
方式一是通过赋值来做 type inference,方式二是通过显式的提供 type 信息。Swift 在 type 的使用上非常苛刻,当之无愧为 statically typed。 显然,static type 比 dynamic type 更安全,编译器可以帮我们做类型检查,这也是为什么 Swift 比 Objective C 在 type safety 上更优秀的原因。当然,dynamic type 并非全无好处,初期开发起来速度会快于 static type,而且省去了编译时的 type 检查,每次编译速度更快。缺点是一旦出现 runtime 中的类型错误,要花更多的时间去调试,要写更多的 test case,准备更多的文档。这种缺陷在较大规模的项目上会更明显,Swift 选择 static type 策略应该也有这方面的考虑。 Type Inference类型推断(type inference)也是 type system 当中的一个常见概念。不少编程语言比如 Swift 都有 type inference 的功能。type inference 有什么用处呢?statically typed 的编程语言决定了变量都必须具备类型信息,意味着我们每次使用变量的时候都需要显式的声明 type 信息,比如在 Objective C中,这样会显得有些繁琐和嗦,一旦有了 type inference,我们可以在代码中省略掉很多关于 type 累赘的表述。我们看如下代码:
这行代码中,有两个实体有 type 信息,变量 i 和常量 0,0 默认的 type 信息是 int,i 的 type 信息没有显示的声明出来,但在 Swift 中,由于 0 被赋值给了 i,所以可以通过 type inference 推断出 i 的 type 信息也是 int。这种类型推断会发生在很多程序员意识不到的角落,这种具备传染特性的 type 信息可以层层叠叠,一级一级的输送到更多的其他变量实体。编译器就是通过这种传染的特性来做 type inference 的。 在 Swift 中,type inference 配合 static type 让代码既精炼又安全。 Optional Type前面提到 type 信息本质上是一种约束,可以避免 type 的使用错误。我们在编写代码时,经常遇到的一种 bug 是对于空对象或者说对象为 nil 情况,漏写了为空的判断。Swift 通过引入 optional type 来强制开发者考虑 nil 的场景,更妙的是,当「是否为 nil 」成为 type 信息之后,编译器也可以一起来帮助检查 nil 的使用场景。看下 optional type 的定义就一清二楚了: |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|