自底向上设计

如今我们陷入了一个巨大的误区,认为学习编程就是学习各种时髦的工具,这些工具有着大量的特性以及难以记忆的细节,导致我们忘记了计算的本质。语言的特性在几年内不断变化,IDE 或命令行工具的参数在不断调整,今天学习的细节到了明年就得换成新的语法。各种号称解决了重大的问题的编程语言层出不穷,到目前为止编程语言有上百种之多,真的学习的完吗?

真正应该学习的是背后的原理,王垠的解谜计算机科学写的很不错。我们不必拘泥于各种语言的细节,我们应该把握核心,理解什么是计算。

“从最广义来讲,计算就是“机械化的信息处理”。所谓机械化,你可以用手指算,可以用算盘,可以用计算器,或者计算机。这些机器里面可以有代码,也可以没有代码,全是电子线路,甚至可以是生物活动或者化学反应。不同的机器也可以有不同的计算功能,不同的速度和性能”

计算的载体不限于现在我们所看到的计算机,任何按照有限的规则进行有限的变换得到结果的东西都称为计算机。理解了这些转而去推断编程语言需要哪些要素就显得顺其自然,甚至可以分辨很多编程语言中有的特性是有害的。我们就可以对语言的特性进行选择,只有那些经过审慎选择的特性才是值得使用的。

既然要避免学习编程语言和各种工具的说明书,我们应该学什么呢?我的答案是学习在未来 20 年都会不消亡的东西,而要预测未来 20 年就看过去 20 年哪些东西还一直存在。 C/C++ 构成了整个编程世界的基石,Linux/GNU 构成了整个开源世界的基石,TCP/IP 则构成了网络的基石,所有这一切存在于过去的 20 年,并且毫无被动摇的迹象,这是我们应该追求的东西。

还有另外一种知识是长久的,关于如何设计软件。早期的软件比较小更为流行的方式是自顶向下的设计,而现代软件已经如此复杂,自底向上是一种被认为更可取的方式。在自底向上设计中 Lisp 是最为彻底的。自底向上编程在 C 语言中表现为抽象数据结构,由最小的语言元素构成一个大的操作对象,在 OOP 语言中则表现为对象。Lisp 除了这种功能上的抽象之外,还提供了改造语言的功能,最终使得语言好像是为了此软件专门设计的一样。自底向上又可以称为组件化编程。假如你想要编写一个图形软件,你大概不会从头完全自己构建,而是引入专门的图形库,图形库中的每个函数以及所附带的数据格式就是独立的、可复用的组件接口。如果你的程序中还需要操作声音,只要引入声音库即可。将这两个组件拼接在一起最终得到了你想要的软件,就好像语言本身就附带了此功能一样。

自底向上的编程哲学在于改变语言以适应问题,语言是为问题服务的。相对于自顶向下的优势在于,最终得到的不是一个单一的软件,而是一个能力更强的语言,以及一个小的软件。语言的能力在不断构建小的组件时得到了增强,同时其抽象能力也得到了增强。先构建出小的组件更加容易测试,可以更早检查修复错误。自底向上编程同时跟高内聚低耦合的原则不期而遇,为了要单独测试这些小组件,设计者必须尽量把可变范围限制在组件的内部,提供给外面一个不变的接口。这些接口就是组件与组件之间的连接,所谓接口就是外面可直接使用的任何东西,典型的例子是函数和数据接口,不少地方也称之为组件说明(Component Specification)。组件与组件之间不单单是平行的关系,很多是嵌套关系。声音与图形之间是平行的,而声音与内存分配就是嵌套的,图形与内存分配或者字符串操作也是嵌套的。大的组件用到了小组件提供的功能。小的组件是复用最多的,这就是大部分现代语言都会提供一个巨大的标准库的原因,标准库中的每一个小组件都会被几乎所有应用复用,从而提高了语言的能力。

大部分的组件被单独设计,并且被单独测试。当最终需要拼合成一个目标软件时已经一件很简单的事。这些写出来的程序更加健壮、更加易读、更加容易测试。自底向上设计应该达到不花费多少力气就能够完成自顶向下,这样设计出来的软件才是最合理的。

自底向上设计的最突出的表现就是设计一系列的库——组件。所以从现在开始就可以去设计一些小的组件以供将来使用。