利用编程语言的类型系统降低复杂性

柏舟   新冠4年 04-27

初学者常常发现动态语言编码简单,其中一个原因是因为无法充分利用静态语言的类型系统,降低心智负担。

当我初学编程的时候,我喜欢使用数组存数据,大脑记忆每个字段的含义。在刚学python的时候,我被python的类型系统震惊了,只学过C的我根本不理解为什么存在dict/map这种数据结构。后来了解了json和lisp才发现使用list和dict居然可以组合出所有想要表达的数据结构。但是,随着代码越写越多,我发现单纯使用list和dict的数据结构并不适合大型项目。

这是因为大型项目对象与对象之间存在一些业务规则,其中一些从属于、继承、引用的关系可以使用类型系统进行约束检查。这时,业务的部分复杂性就转移到类型系统中。而动态语言,虽然初期使用list和dict迅速写出数据结构,但是每个字段的含义和业务规则只通过约定,使用大脑记忆推理,项目后期基本无法维护。

通用技巧:使用类型包装规则

只要数据存在规则,数据之间存在约束,请定义新的类型。

典型的例子是每个物理量单独定义类型,而不是使用double。尤其是对于一个物理量同时存在标准和非标准定义,比如定义角度Angle,就可以通过定义初始化方法区分数据的单位是Radians还是Degree,并且可以实现ToString方法提高Print的可读性。此外,Modelica语言通过检查量纲是否存在错误来辅助编码,Rust也有类似的Units库。

第二个例子是URI、URL,一般的做法是直接使用uint或者int表示ID。ID从int改成string是一个很常见的情况,尤其是用户ID引入第三方认证,你很可能想要在ID加一个标记变成URI,却发现相应的SQL语句使用了int,可能还有一些细枝末节的地方需要修改,最后根本不敢改动。其次,int或string很容易和其它类型搞混,特别是在序列化和反序列化的代码中,很容易忘了处理。使用类型系统可以减少这些错误,当你编码的时候,借助IDE,可以清晰的看到ID和其它类型是不同的。并且如果需求改变,需要增加序列化过程也可以包装到类型的相关方法中,方便维护。

通过将规则包装到类型和相应的方法中,能够让人更清晰的认识到类型的定义域/约束是与普通类型(如int, string)和其它类型不同,并且提高代码的可维护性。如果发现项目中类型太多了,连写代码的人都记不清类型和规则,那就应该思考是系统本身确实复杂还是系统设计本身是一坨屎。

不同语言的类型系统

类型系统基本决定了一个语言的主要领域,它与GC、泛型高度相关。这篇文章偏向初学者,所以有的概念不好深入。而且我有的语言(C/C++,Java)写得很少,说得不对的地方敬请指正。

这个部分主要介绍不同语言的类型系统可以实现的额外约束,从而将业务的复杂性转移。

为什么需要使用类型系统?主要是因为类型系统契合面向对象设计的部分理念。程序员通过与编程语言约定,将一部分心智负担转移到类型系统中,从而实现更复杂的设计。否则很容易导致业务规则的泄漏,在各个函数内部硬编码实现一些序列化逻辑和规则,然后需求一更改,屎山就成了。

也不知道有没有人喜欢看,有没有人对面向对象的设计模式和群论感兴趣。