软件设计,应该包括模型和规范

什么是模型?

模型,是一个软件的骨架,是一个软件之所以是这个软件的核心模型的粒度可大可小,如果把模型理解为一个一个的类,这就是小的模型。把一整个系统当作一个整体来理解,这就是大的模型。关于设计,你一定听说过一个说法,“高内聚、低耦合”,(模块的内聚程度越高越好,模块间的耦合程度越低越好),这其实就是对模型的要求。一个“高内聚、低耦合”的模型能够有效地隐藏细节,让人理解起来也更容易,甚至还可以在上面继续扩展。你在日常工作中用到的各种框架和技术,也是提供了一个又一个的模型,它们大幅度降低了我们的开发门槛。整个计算机世界就是在这样一个又一个模型的叠加中,一点一点构建出来的。这与一些人常规理解的 Controller、Service 那种分层略有差异。但实际上,这才是在计算机行业中普遍存在的分层。我们熟悉的网络模型就是一个典型的分层模型。按照 TCP/IP 的分层方法,网络层要构建在网络接口层之上,应用层则要依赖传输层,而我们平时使用的大多数协议则属于应用层。模型是分层的,即便是在一个软件内部,模型也是可以是分层的。我们可以先从最核心的模型开始构建,有了这个核心模型之后,可以通过组合这些基础的模型,构建出上面一层的模型

什么是规范?

规范,就是限定了什么样的需求应该以怎样的方式去完成。模型与规范,二者相辅相成,一个项目最初建立起的模型,往往是要符合一定规范的,而规范的制定也有赖于模型。它对于维系软件长期演化至关重要。关于规范,常见的两种问题是:

  • 项目缺乏显式的、统一的规范。
  • 规范不符合软件设计原则。

软件设计要考虑“可测试性”

我们知道,软件开发要解决的问题是从需求而来。需求包括两大类,第一类是功能性需求,也就是要完成怎样的业务功能;第二类是非功能性需求,是业务功能之外的一些需求。非功能性需求也被分为两大类,一类称为执行质量(Execution qualities),你所熟悉的吞吐、延迟、安全就属于这一类,它们都是可以在运行时通过运维手段被观察到的;而另一类称为演化质量(Evolution qualities),它们内含于一个软件的结构之中,包括可测试性、可维护性、可扩展性等。
可测试性为什么如此重要?因为我们做设计,其实就是把一个软件拆分成一个一个的小模块。如果不尽可能地保证每个小模块的正确性,而只是从最外围的系统角度去验证系统的正确性,这将会是一个非常困难的过程。我们要保证每个小模块的正确性,就要保证每个模块在开发阶段能够测试,而想要每个模块能够测试,在设计过程中,就要保证每个模块是可以测试的,而这就是可测试性。一旦我们在可测试性上考虑不足,就会引发一系列的后续问题。比如,复杂的系统不仅仅在测试上有难度,在集成、部署等各个环节,都有其复杂性,完成一次部署往往也需要很长时间。这也就意味着,即便是一个简单的验证工作,部署的时间成本也非常昂贵。这还不包括在出问题时,我们在一个复杂系统中定位问题的成本。我们只有把每个小模块尽可能做好,才能尽量降低对集成环境的依赖程度,从而节省后期的成本。
那么如何在设计中考虑可测试性呢?其实就是要在设计时想一下,这个函数 / 模块 / 系统要怎么测。当你用这个标准衡量一些系统时,可能就会发现一种典型的错误,就是设计根本没有考虑过测试。这样的系统常常只有最外层的接口可以测试,也就是说,整个系统必须集成起来才能测试。在实际工作中,很多公司为了做集成测试,要把所有的子系统全部都搭建出来,也就是一套完整的环境。这种环境要占用大量的资源,一般来说,公司不会准备很多套。这样造成的结果就是各个团队对于环境的竞争,再叠加上各个系统配合的问题,测试的效率还会进一步降低。所以,我们在设计一个函数 / 模块 / 系统时,必须将可测试性纳入考量,以便于能够完成不同层次的测试,减少对集成环境的依赖。那么,具体该如何做呢?一方面,尽可能地给每个模块更多的测试,使构成系统的每个模块尽可能稳定,把集成测试环境更多地留作公共的验收资源。另一方面,尽可能搭建本地的集成测试环境,周边的系统可以采用模拟服务的方案。在软件开发过程中考虑测试,实际上是思考软件的质量问题,而把质量的思考前移到开发,甚至是设计阶段,是软件开发从传统进入到现代的重要一步。