45 星推荐

用了大概一周多的时间看完了,一本简明的工具书,从不同的编程范式说起,指出了构建软件时应当遵循的规则,以及为什么应当遵循。这篇是在看完合住书的情况下按照回忆来写的,写着写着就断掉了,苦不堪言,还是要时不时的回去看书啊。

相对于硬件,软件应当「柔软」,不与运行平台绑定,对新需求友好。

编程范式有三种,面向过程编程,面向对象编程,函数式编程。这三种范式都是通过约束程序员的能力,换取程序的健壮性。面向过程编程约束了 goto 语句,不允许程序执行流程随意跳转,将函数的控制语句限制在 if...else.., for..loop 这几种,保证了程序的可推导性;面向对象编程约束了函数指针的使用,多态取而代之,使得依赖反转以及插件式设计更加容易;函数式编程约束了变量的可变性,即在函数式编程思想中,变量一旦赋值是不可更改的,从而在根源上避免了各种多线程并发问题。

编程范式在源码层面保证了整洁,而架构设计也需要整洁。

开发系统时,划分组件应当遵循的 SOLID 原则:

  • SRP 单一职责原则,同一个组件的代码实现的功能应当同属一个职责,同时也应当兼顾变更速度,否则会导致一个小的改动引起其他功能的错误或者至少是更多的回归测试
  • OCP 开闭原则,对扩展友好,对修改拒绝。开发组件时应当考虑如何设计才能够使得以后引入新功能时,可以通过扩展实现,而尽量降低修改对原有代码的影响。一种模式是支持插件式设计。另外的考虑是,对依赖方向的控制。一般来说频繁的需求变更都不会影响核心业务逻辑,因此将核心业务逻辑放在依赖方向的最上层,依赖只会从下往上,以此下推,新需求只会影响下层的模块,而不需要大规模修改核心逻辑部分。
  • LSP 里氏替换原则,继承实现的指导原则,在可以使用父类的地方,使用子类替换后,行为不能异常。
  • ISP 接口隔离原则,通过接口隔离依赖,可以减少很多不必要的麻烦。
  • DIP 依赖反转原则,控制流的依赖应当遵循依赖守则:单向的、由下层指向上层。而源码依赖和控制流依赖的方向通常是相反的。

划分组件时,我们一般会想到 MVC,model、view、controller,这是按照流程功能来做的归类,与特定系统处理的领域无关,即不管是哪个领域的软件系统,都可以按照这个模式划分为三块。但是这样却不够合理,一个系统的组件划分应该有自己鲜明的特征,就像建筑图纸,如果你看到了客厅、厨房、卧室,那这无疑是家的设计,而如果看到的是借阅室、还书区、公共阅读室那这肯定是图书馆。

类似的,系统的组件划分应当是和它的领域相关的,一个新来的同事,应该可以很快的发现这个系统要做的事情。因此划分系统组件时,应当是用例主导的,并且不同用例的功能应当遵循单一职责原则进行隔离。识别出核心业务逻辑,所谓核心,就是这个业务中与赚钱最为相关的一部分逻辑。梳理依赖关系时,核心逻辑应当处于最顶层/或者是同心圆的最里层,其他模块依赖它,而它不会依赖下层的模块,下/外面一层是用例的处理逻辑,再外一层是展现/存储的转换层(presenter),最下/外一层是真正的展现/存储。

引入转换层,是因为 GUI 是难于测试的,而测试应当是系统的一部分(是的,系统不只包括开发功能的代码),为了避免出现系统无法测试的情况,展现部分应当没有任何处理逻辑,这些逻辑应当放在转换层,从而将系统从转换层这里划分为可测试部分和不可测试部分。而测试在系统中所处位置,是属于最底层/外层,它依赖于所需测试的模块,而没有模块依赖于它。但是测试不应当与功能模块耦和,避免出现功能模块一个小的改动,导致测试模块成千的失败。另外,必要时,应当为测试编写专门的 API,以便跳过各种安全、权限检查。

GUI 只是一种展现方式,而 Web 只是一种 GUI方式,因此设计系统时,这部分属于实现细节,决策应当向后推迟,以便支持更多的展现方式,或者说遵循 OCP 原则,在后续有新的展现需求时可以更容易扩展。另外的展现方式还有 CLI 。

数据库也只是一种实现细节,数据库的表/行结构如此流行,只不过是因为内存不够大/硬盘不够快,如果没有这样的限制,应当尽可能选择利于程序使用的结构,比如栈、堆、数组、哈希表等。另外的存储方式包括文件、缓存等。因此关于存储的决策也应当尽可能延迟。

不要迷信框架,对框架的使用程度,可以使用框架便利开发,但是不应当在业务逻辑中出现对框架的依赖,即保持框架可以方便地被去除/替换。

架构的价值在于,使得系统开发的成本尽量降低。

在系统全周期(开发、部署、运行、维护)中,好的架构都发挥着作用。开发时,系统架构应当和开发团队相匹配,方便系统开发;部署时,保持不同组件的独立性,修改其中一个不需要全部重新部署;运行时,好的架构应该可以起到解释系统运行过程的作用;维护包括系统的捉虫以及新需求的 patch,是整个周期中成本最高的,好的架构通过遵循开闭原则以及接口隔离原则,使得 debug 可以尽量少的影响原来代码,并方便扩展新加功能。

好的架构应当使得系统的可选项尽可能久的尽可能多。