软件随想录:代码与数据

时间:2022-05-06
本文章向大家介绍软件随想录:代码与数据,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

在程序的世界里,代码和数据犹如一对孪生兄弟,你中有我,我中有你,有时候不大分得清楚。如果你研究过 linux 的 ELF(Executable and Linkable File)文件结构,或者对程序的加载运行有所了解,那么你一定知道,所谓的代码段(text section),数据段(data section)只不过是从安全上/性能上权衡之下的人为划分:代码段除了被标记为可执行和只读外,和数据段并无二致。更有甚者,如 ARM compiler,为了让立即数的加载可以用一条语句完成,还将这样的数据混编在代码的末尾,用 PC + offset 寻址的方式来获取数据。可见,在机器的眼里,世间本无代码,亦无数据,有的只是 0 和 1。

从软件开发的角度看,我们需要适当的层次,以便于低层海纳百川,兼容并蓄,而高层设定规范,聚焦问题。所以层次越低,看到的越倾向于数据;而层次越高,看到的越倾向于代码。我们都知道,很多时候,解决一个问题的最好的方式,是先构建满足这个问题空间的 DSL,然后用 DSL 撰写代码来处理具体的问题。表面上看,相对于通用的编程语言,DSL 将开发者桎梏在一个特定的框框里,不够灵活,然而,就像 buffer 能够表述几乎一切数据结构而我们并不愿意在任何场合使用它一样,更多的限制带来了更好的结构。

跟面试者聊技术时,如果对方用过 ORM,我必然会问一个关键的问题:ORM 较之于 SQL,除了能够免疫 SQL injection,还有什么好处?遗憾的是,大部分人的回答都在外围彷徨,没有触及核心(放心亲们,这个问题我面试时不会再问)。我们用 ORM,或者 LINQ,而尽量减少 SQL 的使用,是因为我们通过把 SQL 这种嵌在代码里的字符串数据,转化成了代码,也就意味着我们拥有了编译时或者运行时的检查,乃至编辑时的检查 —— 各种 linter 很难检测出字符串中的SQL语法或者语义错误,但可以在你撰写 ORM 代码时便提示你其中蕴含的语法/语义错误。这是非常伟大的一个进步 —— 对于软件工程来说,越早发现错误,消弭错误所花费的时间就越短。

同样的道理,jsx 较之 template 的优势是什么?仔细想想,尽管各种 template engine 里面能够混入代码,比如 handlebars 的 #each,template 的本质还是数据;而 jsx 不消多说,大家都知道它是代码。在 template 里,你写错一个 attribute,世界不会因此而停顿,你的程序甚至还会运行地很好,但埋伏了一颗哑弹;在 jsx 里,连传递了错误类型的 props(如果你认真定义接口类型的话),程序都会在编译期罢工并指明错误所在。

代码较之数据的另一大优势是,代码可以轻易组合(compose),而数据很难。我们还以 jsx 和 template 作对比。尽管 template 有 fragment 的概念以促进重用,但稍微复杂一些的情况,template 便难以为继;而 jsx 作为「函数」(或者说组件),可以轻松升级到「高阶函数」(或者说高阶组件 High Ordered Component)。此外,template 对于行为的表现能力和复用能力很弱,而 jsx 可以轻松 mixin 或者 decorate。

举个 HOC 栗子:

这种将问题拆分到更小的粒度,然后再组合起来的能力,是 template 下的 fragment 无法做到的。

代码对数据的优势显而易见,那么劣势呢?

之前讲到,更多的限制带来了更好的结构。然而,这限制有时是把双刃剑,扼杀了灵活性。(这就是写代码的矛盾:太灵活失了结构,太结构化失了灵活性)

比如说对于同一个数据,我们需要将其序列化(serialize)成不同的结果,你可以这么写代码:

然而,当你需要增加一种新的类型的时候,你需要重新修改这段代码,这并不符合 open-close 原则。这个时候,你需要更多的灵活性,而非控制力。你可以通过抽取代码逻辑,将其转为 Map 的方式获得这种灵活性:

当然,这种方式有些啰嗦,type.json 里有太多重复的,不必要的信息。一种处理方式是 CoC(Convention by Configuration),缺省情况下,认为某个 type 的 serializer 在./serializer/${type}.js 下找。所以上述的代码可以进一步优化成,无须 type.json

从上面的讨论来看,什么时候用代码,什么时候用数据,要根据问题来看,不能一概而论。不过,有一些原则要把握:

  • 能用代码(或者高阶的形式,如 DSL)表述的问题,且不引发灵活性的担忧,使用代码;
  • 在撰写阶段容易引入错误的,优先使用代码(比如 jsx,ORM);
  • 如果要使用数据来表述逻辑,为数据设置严格的类型,并使用 validator 验证之(如 joi)。

嗯,就先随想这么多。