2020.08.14
2387
fish
有太多的文章教你怎么组织代码了。但是这些文章大都是系统A,模块B的抽象写意派。虽然看着很有道理的样子,但就是看不懂。
有太多的文章教你怎么组织代码了。但是这些文章大都是系统A,模块B的抽象写意派。虽然看着很有道理的样子,但就是看不懂。 本文的特点是有十多个带有具体业务场景的例子。从如何接新需求的角度来分析模块应该怎么拆分,才可以优雅地复用,延长遗留代码的寿命。 主要的内容都在例子里,请不要直接看结论,相信我,只看结论等于没看。
全文分为四个章节:
复用,以及支持多样性,都是同一个问题的不同表述。其实质问题是如何对系统进行模块分解。需要分成几个模块,模块之间的依赖关系是怎样的? 我们通过4个例子来非常具象化地讨论。
在 Agile Software Development 书中,Robert Martin 讲过了很重要的两个原则
模块与模块之间的依赖关系,就是抽象与稳定的关系。但实践中,像“业务编排API”和“BFF”,你很难判断谁比谁更稳定,更抽象。当我们一个业务请求,需要经过一串模块的时候,往往是有问题的。因为当要做修改的时候,你会觉得在哪个环节拦一刀都有道理。David Parnas 在 The Secret History of Information Hiding 一文中也写道,他认为 Levels of Abstraction 是很难判断的。
这个例子应该怎样调整是合适的?分法有很多,可以按流程步骤分,可以按业务变化频率分,但从依赖关系的结构上来说,一定是这样的结构
不会因为把函数调用,改叫“业务编排”,就改变模块之间的依赖关系。依赖关系才是真正决定性因素。
从这个例子里我们可以看到如下的规律
稍微有点经验程序员都能体会到 vscode 做为 Eric Gamma 大神在 eclipse 之后的又一力作,架构上是很优秀的。但可能只是感觉优秀,又说不出来优秀在哪里。通过这个例子,我们就可以看到,判断一个模块拆分结构是否优秀的唯一标准,就是看它如何处理需求的变更和新增。当所有的需求都要往一个模块里改的时候,这个拆分就是糟糕的。当新的需求往往可以通过新增模块来实现的时候,这个拆分结构就是优秀的。
这个例子说明了
模块切分的出发点其实非常简单直白
综上,在这样的一个依赖关系下
经常修改的应该是中间这一层。最好是一个需求只添加,或者修改其中的一个模块。
模块划分的静态结构无所谓好坏,只关注新需求如何修改或者新增的问题。 不用去争辩是应该大前台,还是大中台。代码量不是问题,圈复杂度也不是问题。 唯一度量的标准就是去看每个新需求都是怎么改出来的。
上一章提出的好坏标准有任何一条是新鲜的么? 一条都没有。 以前的文章可能例子举得少了一点,但是总结的原则都是差不多的。 那接下来的追问就是,如果原则一直都在那里,那为什么我们过去看过的代码都没遵守这些原则呢? 我有三个猜测
我先来分析第一个猜想,列举几个最常见的解决方案。
这个例子里说明了两个现象:
如果这种一个模块需要f(args)传递一个很大的结构体给另外一个模块的方式是不理想的。那么更理想的模块间接口形式是什么?有些文章会把运行时的RPC性能,系统不宕机,和不易于独立做需求变更放在一起讨论。但是我认为这样混到一起来讨论问题会误入歧途。比如是不是数一数两个模块之间的 RPC 调用的数量,甚至是从运维系统里导出一份运行时的 RPC metrics 就可以说明两个模块的耦合程度呢?
应该聚焦在“新需求怎么接”这一个问题上,不要躲闪,不要旁顾左右而言它。其实就是看一下,两个模块之间边界无论用什么来定义,是不是经常要被修改。用 interfac 关键字代替 class 关键字不会有实质性的作用。用 gRPC 代替 jar 包也不会有实质性的作用。
阿里巴巴公司有一个名字叫“中台”的技术。
这个例子说明了两个现象:
这个例子说明了:
上面的三个例子的共同特点就是用一个形式代替另外一个等价的形式。 调整打包和部署方式是容易的。 调整模块边界,重塑接口,这个是要触及灵魂的。痛彻心扉。 最容易想到也是最容易办到的方案,未必就是最好的方案。
上一章我们看到了,只是改变依赖的“形式”,不会影响依赖的“实质”。 如果要让模块之间更好组合,最终仍然是要去调整模块之间的边界,也就是要把“模块接口”定义成“松耦合”的。 “松耦合”已经是陈词滥调了。能不能用具体的例子来说明到底这样的接口是长什么样子的?
这个例子展示了两个最实用的技术:
无论是“UI组件透传”,还是“id透传”,从耦合级别上来说都是最黑盒的那种。
这个例子说明了两点:
这个例子说明了两个技术:
当“UI 组合”这种纯黑的方案不行,“不要返回值”这种半黑的方案也不行,那么伪装成存储是需要模块间双向通信的前提下的比较优的解决方案。
这是一个真正拷问灵魂的问题。
前两天在朋友圈刷到一句睿智的话
行得通的理由
行不通的理由
我们要客观地看待复用和支持多样性。很多时候不复用就是最佳的解决方案。很多时候堆砌 if/else 就是最佳的解决方案。
在保持客观理性的同时,能不能把“行不通”的种种障碍逐一分析以下,给每种类型的障碍提供一个切实可行的解决方案呢? 也就是我们能不能给一个设计模式的列表,所谓设计模式就是“问题清单”。
如何才能像大师一样,上来就知道抽象的接口应该如何定义? 我为什么总是想不出来该怎么抽象?
为了代码复用,拆了很多个模块,导致代码不好阅读了怎么办? 之前代码虽然写的挫,但是 ctrl+f 在一个文件里就可以找到对应的代码,现在找段代码可费劲了。
开槽,开插件,都是为了把代码写得更灵活。但是每一开一个扩展点,就需要写一堆样板代码。如何才能降低“插件税”呢?
经常看见一个表里面有 extra_fields 之类的字段,里面放一个大 JSON。每个需求都要加新字段怎么弄?
为了支持新的业务,往往需要给 OrderType 这个字段上添加新的订单类型。为了不影响已有的业务,还经常要加 isNewBiz 这样的“Flag”来标识新的业务场景。除了一直加新的Flag,就没别的办法了吗?
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。