之前说了架构的价值在于让软件更易于变化,更易于应对未来的需求。但易于变化这个属性和软件的功能代码本身是矛盾的。所以架构本身是矛盾的。

如果我什么都没有,此时根本就没有任何代码,那么在这种情况下我其实是最能易于变化的,因为什么都没有,以后要写什么,怎么写,怎么样都可以。相反如果我已经有了一个很重的软件,那么这个很重的软件本身的很重的代码其实就限制了你未来新功能的写法,因为任何的修改都会影响到现有的东西从而带来很大的修改cost。

但我又不能什么都不写,所以这实际上就把问题规约到我到底要写多少东西,要把这个软件写到什么程度,才能易于变化。根据上面的讨论,其实答案很明显,就是写的代码越少越好,越少的代码就越能更易于变化。

那么越少的代码具体的定义是指的什么呢,我认为其实就是指的能实现现有需求的那么多代码就行了。所以一开始把事情想太复杂是没什么意义的,如果一开始就假设了很多的需求,弄了一个有很多概念的框架,妄图让这个框架来囊括未来的所有的需求,这基本上是不可能的。因为假设的需求,属于overconfident,都不知道以后会怎样那么最好的办法就是不去定义它。根据奥卡姆剃刀,我们的假设必须越少越好。而且如果框架太大,搭框架就花了很多时间的话,客户是看不到你的框架的,无法让客户一个cycle一个cycle的看到进度的成果,就违反了敏捷开发了。

The most important purpose of architecture is to let us can defer the making of tech decisions to the time when we can obtain enough information.

那么我只要实现这么多代码就行了,那么能不能再给这些代码分分类呢?那么显然这些代码里最重要的代码就是业务逻辑代码,剩下的代码都是为核心的业务逻辑代码服务的。以后如果来了一个需求,我们会改什么呢?很大概率会修改业务逻辑代码,业务逻辑代码的变更同时引起了其他dependency的变更。

架构师的最重要技能就是判断哪些东西是属于业务逻辑,这需要抓本质的本领。这里面最关键的是要判断出业务逻辑的大致范畴以及今后可能的变更方向。当我圈出业务逻辑之后,我就需要把业务逻辑和其他细节隔离,简化他们之间的交互。这样如果其他细节要改,也可以最大程度的不影响业务逻辑。这里说到底,架构就是隔离的艺术,也就是模块化的艺术。或者,按照我的说法,就是把东西分类的艺术。

bob大叔告诉我们,万变不离其宗,最核心的就是业务逻辑,剩下的东西都不属于核心,都要被隔离开。这里他用的概念是policy和detail,业务逻辑本身是policy,它主导一切,而其他的所有都是detail,架构关心的是policy和detail的分离。我们要不要用数据库,要不要用微服务,UI要不要用Vue还是React,要不要用REST,这些都是detail,detail都是可以被***defer***的,越晚越好。

The goal of the architect is to create a shape for the system that recognizes policy as the most essential element of the system while making the details irrelevant to that policy. This allows decisions about those details to be delayed and deferred.

要实现这个目标,就需要模块化,然后在模块依赖间引入接口,将模块间concrete class的耦合降到最低,简化模块间接口的复杂度并且在适当的模块间实现依赖反转。说穿了,这就是架构的全部内容。

既然模块化了,那么模块间是有调用依赖的,有些模块不依赖任何其他东西,而有些模块依赖很多东西。如果一个模块被很多模块依赖,那么它应该是最不能修改的,应该是最稳定的,因为修改它的话依赖它的整个依赖chain上的东西都要被修改,这是相当于重写的cost。架构师的职责就是合理的设计依赖chain,让最底层的,被依赖最多的东西,尽量在未来不会被修改,让被依赖的少的模块,承担更多的修改责任。

其实这个依赖关系和现实情况非常吻合。我们最底层的被依赖最多的模块是什么呢,往往就是那些通用purpose的哪里都会被用到的library,而这些library在未来被修改的概率本来就是极低的。

当我们画出了依赖关系图,那么我们从哪里开始implement呢?从依赖图的中间吗?如果是这样的话那么你就需要假设你依赖的部分的接口。万一以后发现假设的不对呢?这个模块一改是不是上面所有依赖这个模块的也要跟着改呢?

所以这里应该采用拓扑排序的思想先实现最底层的没有任何依赖的模块,这就是所谓的自底向上的实现路径。只有这样你才能像堆积木一样一层一层的把东西搭出来,万一搭错了我推倒的层数可以做到最少。

自顶向下的方法不能用于软件的implementation,因为这是worst的方法,当然我们可以依靠自顶向下的方式来分析软件的逻辑,因为analysis本身是必须从最general到最detail的。但自顶向下只能用于分析,而不能用于实现,或者design,实现一定是自底向上的归纳法。

T***he component structure cannot be designed from the top down.***

If we tried to design the component dependency structure before we designed any classes, we would likely fail rather badly. We would not know much about common closure, we would be unaware of any reusable elements, and we would almost certainly create components that produced dependency cycles.

自顶向下的design,其实无处不在,比如说所谓的ppt架构师。ppt里有什么呢,没有任何detail,只是画了几个框告诉你有多少个模块,然后再画几根线把模块连起来,仅此而已,没有任何detail,没有detail就意味着没有什么有效的information。而真正的自下而上的design必须是class diagram先行,只有class diagram才会包含有架构的detail,而只有在不停的写code去尝试各种class diagram之后,才能得到更为合理的class diagram。

bob大叔对于component提出了一堆理论,比如说下面的图,里面最中间最核心的Entities就是业务逻辑。书中剩下的内容过于琐碎,而我更关心最核心的东西,所以也不细说了。

cleanarc.jpg

上面加粗斜体的都是bob大叔原文。

最后,bob大叔还说过:

Frameworks are details.

Don't marry the framework!

Don't let frameworks into your core code.

我的这篇旧文从直觉上表达了同一个意思:
保留变化

上一篇 下一篇