代码编织梦想

个人博客

软件的复杂性

什么是复杂性?

举个直观的例子:1+1+1+1 显然比 1+1 要复杂一点,1+2+3+4 显然又比 1+1+1+1 要复杂一点。

这里可以总结两点:

  1. 更大的能力带来更大的复杂性;
  2. 整体的复杂性为各局部复杂性的加权和。

直观地说,对于软件系统,如果难于理解和修改,那么它就是复杂的

有三个症状可以诊断系统的复杂性:

  • 变更放大,一个简单的变更需要修改很多不同的地方;
  • 认知负担,完成一项任务需要理解很多东西;
  • 不知道自己不知道,需要修改哪块代码或掌握哪些信息并不明确。

我想大家在项目开发过程中都遇到过这种情况,明明看起来是一个很简单的修改,但实际改起来却发现是一个大工程——最近的一个项目就有这种感受。细究起来不外乎两种原因:1、这个修改本身的技术内涵比较复杂;2、系统原来的设计有问题,导致后续修改牵一发而动全身。这个直观定义还可以放大到整个研发体系,如果整个体系难于理解和执行变更,那么它就是复杂的。复杂系统最大的问题是事倍功半。

导致复杂性的原因有两个:

  • 依赖(可以理解为耦合),一个代码块 A 不能被单独地理解和修改,必须要结合 B,那么 A、B 之间就有依赖。依赖导致变更放大问题和认知负担问题。
  • 晦涩,关键信息不明显,如命名不准确、依赖不明显。晦涩导致认知负担问题和不知道自己不知道问题。

随着系统存在的时间越来越长,依赖和晦涩会随着不断叠加的需求而不断累积,经过一个滚雪球的过程,后续任何对系统的修改都会充满了风险和困难。

如何应对复杂性

此处将 method、class、module、service、subsystem 等都视为模块。

模块要深

在现实的模块化编程中,开发一个模块是为了提供给别的模块用,所以应包含两部分:对外的声明(interface)和对内的实现(implementation)。interface 是对本模块能力的抽象,是其他模块在使用该模块时需要知道的信息,应该设计得比 implementation 简单得多。interface / implementation 越小,模块的深度就越深,否则就越浅。

在设计模块时,应该尽量设计深度模块,不重要的内容都抽象掉,尽量减少相互之间的依赖,想想如果 interface 和 implementation 差距不大,那就失去了将其包装为一个模块的意义。

和目前强调尽量设计小的类的流行设计方式相比,深度模块有助于减少类的数量,不过很难说谁绝对正确,毕竟分离不同变化频率的部分也是很有益处的,那么到了具体场景就看设计者具体问题具体分析的 trade-off 功力了。

信息隐藏

实现深度模块采用的方法就是信息隐藏。信息隐藏将模块的能力简化为简单的 interface,减轻了认知负担。同时被隐藏掉的实现细节即使发生变化,因为 interface 不变,其他模块也不受影响,使得系统更容易进化。例如我们调用的 restful 接口,我们并不关心提供接口的服务使用了什么操作系统、什么数据库、什么编程语言。

泛化模块

设计模块时有一个权衡:是设计专门针对当前需求的特化模块,还是设计为支持未来扩展需求的泛化模块

聪明如你马上会想到某位大师曾经曰过:过早优化是万恶之源。但我们还是要在边缘做些试探,因为提前考虑可以在未来节省时间,恰如 CPU 偷偷干的分支预测。

因为模块分为 interface 和 implementation 两部分,所以这里的建议是:泛化 interface + 特化 implementation

通过特化的 implementation 来满足当前的需求,通过泛化的 interface 来支持未来的扩展,这样的模块也是一个深度模块。比如我作为中间商,对外提供支付接口,不会具体说这个接口是针对哪家银行的(特化),而是提供和具体银行无关的支付域操作(泛化),这样后续本地的支付细节随时可以变。

分层抽象

分层抽象是计算机领域的经典思想,每一层都关注不同的问题,对应不同的抽象,上层只能调用相邻下层的接口,下层不能调用上层,最典型的案例就是 OSI 分层模型。

当分层后发现相邻层存在只是传递了参数给另一层的调用情况时,说明两层的模块之间职责划分有问题,增加了复杂性但没有增加能力,需要调整。对于 Dispatcher、Decorator 的设计来说,虽然 interface 重复,但增加了能力,所以认为没有问题。

当存在一些参数需要跨层传递时,有 4 种方式可以选择:1)逐层传递参数;2)共享变量;3)全局变量;4)共享 Context 对象(Spring 里的各种 Aware)。建议选择第 4 种。

下移复杂性

对于一些配置参数,如果模块内部自己决策能比使用该模块的用户决策得更好,那就内部消化掉,不要暴露给用户去决策。

异常设计

名侦探柯南:真相只有一个。—— exception 却有很多。

异常会增加复杂性,最好是减少必须要处理异常的位置。次之有几种减少(因异常而引入的)复杂性的手段:

  • 不定义异常,如果没有必要,就尽量不要定义为异常;
  • 隐藏异常,如果在发生异常情况的较低层次能处理好,就不要对高层抛出异常;
  • 合并异常,使用同一段代码处理多个异常;
  • 直接 crash,对于一些根本无法处理也不常出现的异常情况,在出现时不如直接让应用 crash 掉。

未完待续......

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/alading2009/article/details/127153114

the design philosophy of the darpa internet protocols(英文原版)_北北南北的博客-爱代码爱编程

本文转载于 http://blog.sina.com.cn/s/blog_691314b20101qgss.html David D. Clark Massachusetts Institute of Techn

读书笔记:a philosophy of software design (二)-爱代码爱编程

2019独角兽企业重金招聘Python工程师标准>>> 接着上次的分享 设计两次 这里“设计两次”的意思是无论设计一个类,模块还是功能,在设计的时候仔细思考,除了当前的方案,还有那些其它的选择。在众多设计中比较,列出各自的优缺点,然后选出最佳方案。就是对于设计方案,都有两个或者两个以上的选择。

读书笔记:a philosophy of software design (一)-爱代码爱编程

2019独角兽企业重金招聘Python工程师标准>>> 今天一位同事在斯坦福的博士生导师John Ousterhout (注,Tcl语言的设计者)来公司做了他的新书《A Philosophy of Software Design》的演讲,介绍了他对于软件设计的思考。这里我把本书的读书笔记和心得分享给大家,欢迎大

Tips from A philosophy of software design-爱代码爱编程

Design Principles Make investments in design Complexity is incremental: you have to sweat the small stuff.Working code isn’t enough.Make continual small investments to improve s