最近文章写得少了,都是因为在填耦合架构的坑,人生苦短,填坑不完,o(╥﹏╥)o,对如何治理耦合架构感兴趣,可以看我之前的文章——比耦合架构更好的架构
好不容易从耦合坑里爬出来,又踩到了多子类型的坑,大坑套小坑,放眼望去,我太难了o(╥﹏╥)o
本文结合真实项目经验,介绍多子类型的架构演进,将会介绍四种架构的优缺点和适应场景,分别是耦合架构,正交架构,低代码架构,低组件架构
故事是这样的,某一天小颜同学接🤚了一个业务M,小颜同学后来才知道,这个业务在团队内部被称为“屎山”,也就是传说中的多年迭代,多人接手,多种组合,缺少文档的烫手山芋,简称三多一少
M系统是典型的多子类型,M系统管理M资源,M资源有100多个子类型,每个子类型都有自己的逻辑,大家有类似业务同学可以感同身受一下
M系统的现状是100+子类型,2万行代码,但这2万行代码是写在一起的,这个文件内部有2000个分支,OMG,每次打开这文件,编辑器都要卡一会,就给某个子类型加一个逻辑,看了一天还不知道该往哪加,下图是小颜同学下班时的心情
就这样熬了一周,小颜同学深感写代码就像在扫雷,每天都在想,应该改哪里,改了会不会触雷,我的天啊,代码成功了
比代码出错了不知道为什么,更可怕的是代码成功了,也不知道为什么;那么问题来了,如果是你面对这种情况该怎么办呢?
- 相似又不同的类型
- 快速增长的业务
- 我的代码该如何写?
耦合架构
像这种把子类型的逻辑都写在一起的模式,我称其为耦合架构,需要注意的是耦合也十分级别的,同样是耦合松耦合和紧耦合也是不同的,如果对于耦合的分类感兴趣,可以看下我的另一篇文章——图解7种耦合关系
PS:我的另一篇那文章也提到了耦合架构,感兴趣的可以看看——比耦合架构更好的架构
一般听到耦合,第一反应就是解耦,但是先思考一个问题,我们的代码是如何失控的?
系统很有可能是这样演进的,在业务开始时
- PM提了一个子类型需求M1,M1包含标题和音频
- PM提了一个新的子类型M2,在M1的基础上多了一个视频
- PM提了一个新的子类型M3,在M1的基础上多了一个图片
写下第一行代码的同学很快就完成了M1的开发,当面对M2和M3时,优先会考虑复用M1的逻辑,极有可能选择在M1中加上M2和M3的逻辑
整个系统就这样慢慢的演进,随着时间的积累,慢慢走向失控
- PM提了一个子类型M4
- …
- PM提了一个子类型M50
- …
- PM提了一个子类型M100
终于有一天变成了现在这样,也许早就有同学发现了系统的问题,但未能及时解决问题,相信不少业务会有这种年久失修的问题
现在整个系统紧紧耦合在一起,巨量代码和逻辑交织在一起,整个系统蕴藏着巨大危机,开发效率低下,几乎无法并行开发,因为没法解决代码冲突啊;维护风险很高,真正的牵一发而动全身
整个系统已经到了岌岌可危的处境,急需一个人挽狂澜于既倒,扶大厦于将倾
如果想解决问题,首先我们要理清问题,这个系统目前存在如下问题:
- 新人上手成本(看不懂)
- PM让我修改旧类型(改不动)
- PM说这个可以复用另一个(理不清)
但是这些都只是问题导致的结果,导致这些问题的根源是,一个类型的需求,要面对全部类型的逻辑,系统复杂度:O(n^2)
其实前人们也一直在努力解决问题,只是努力的方向是,在现有架构下修修补补,比如总结文档,代码上面进行归类抽象等
正交架构
正交架构的思想其实非常简单,既然要解耦,那就直接把每个子类型的逻辑分开实现不就好了吗,代码上彻底隔离
正交架构其实就是分治思想,分治思想提倡将大的问题,分离成多个小问题,从而分别解决,在多子类型系统中,子类型就是一个绝佳的分治媒介
正交架构带来的直接好处就是,子类型解耦了,子类型内部逻辑是自治的,相互之间没有关联,从而使每个子类型的复杂度降低了,虽然系统里的逻辑和代码量并没有减少,但系统的整体复杂度降低了,在我们这里例子里面,用正交架构替换耦合架构收益如下:
- 复杂度 n^2 => n,增加子类型时,系统复杂度线性增长
- 代码量 2个数量级,修改一个子类型时,代码量由万行级别 => 百行级别
- 分支数量 3个数量级,系统内部逻辑分支由千级别 => 个位数
既然正交架构整么好,那为何不用正交架构替代耦合架构呢?没错,如果是新系统的话,我建议你从一开始就选择正交架构,但对于存量系统就很麻烦,因为有巨大的历史包袱,在这种情况下,如果时间紧迫,我建议先从新类型切到正交架构,历史包袱先保留不动,正可能需要一点小的代码设计才能实现,但我相信难不倒你的
接下来说说正交架构的问题,通过把子类型的代码分开,带来一个明显的问题,在耦合架构中,类型之间的公共逻辑和组件是复用的,但在正交架构中是重复的,特别是对于复杂的逻辑和复杂的组件,问题尤为明显,一旦这部分功能要统一修改时,那可能要在每个子类型都要修改下
这个问题可以通过将公共组件和公共逻辑抽象出来的方法来解决,一般如果2个子类型重复的部分,就应该提取出来,在业务迭代中,如果你想复用另一个类型的功能时,就是抽象的合适时机,一般PM会提醒你这个事情的,PM可能的对话如下
小颜同学,这个新类型的这块就和之前的一样,我就不用在描述了吧 —— 传说中的一句话需求
关于公共组件化,还有一个问题不得不提,有两种抽象公共组件思路,一种是,组件提供配置,不同子类型使用组件时,配置不同开关;一种是,把子类型作为参数,传递到公共组件,组件内部判断子类型实现不同逻辑;对于前一种,我称为纯组件,对于后一种,我称为非纯组件
我发现不少同学在提取组件时,会写出来非纯组件,其实非纯组件只是把代码物理隔离了,逻辑上并没有隔离,在你的公共组件里其实还是一个小型的耦合架构,所以建议大家选择纯组件
除了上面的非纯组件问题,正交架构还有个最大的问题就是组件化是可选的,这其实给拷贝代码提供了可能,让重复代码有继续存在的温床,提取组件这个事情就是一个最佳实践,属于弱约束的事情,而弱约束一般只能通过代码评审发现……
后面可能发生的事情大家都懂了吧
低代码架构
整个系统在正交架构下跑了一段时间,顶住了业务的压力,但我一直在思考有咩有更好的架构,最近低代码如火如荼,多子类型有咩有转低代码的可能?
说来也巧,刚好业务要对系统进行大的改造重构,在这个过程中,我落地了低代码架构,对于多子类型业务,如果其类型之间存在一些相似或重复部分,那么可以考虑低代码架构
对于单个子类型来说,只需要将正交架构中的代码,全部替换为配置,配置可能会对应一套DSL语言,需要搭配一套渲染器,渲染器读取配置,将每个配置渲染成组件库里的组件,就形成了完整的闭环
我发现低代码刚好解决了正交架构系统可能存在的两个问题,低代码架构强制系统必须全部组件化,这就避免了正交架构可选的组件化可能带来的同样逻辑,实现不同问题;同时低代码架构下,强制要求组件面向配置开发,这恰巧杜绝了非纯组件的问题
低代码这一套铺下来,整个系统的复杂度和子类型数量的解耦,整体收益如下:
- 复杂度:O(N) => O(常数),N是子类型数量,常数和组件数量相关
- 开发效率:80% ↑,新类型大概率不需要开发了
- 强制纯组件化
但低代码也不是没有缺点的,首先整个系统的架构会比正交架构复杂,开发难度更大;低代码系统会增加额外的开发工作,比如配置系统,DSL语言和渲染器的开发等;如果遇到系统不支持的组件,需要额外的开发成本,且开发成本会大于这个组件在正交架构下的开发
低代码架构中有几个关键的技术难点,这里简单提一下
- 绑定数据能力
- 联动能力
- 组件可扩展能力
- DSL可扩展能力
- 校验如何设计
- 组件设计原则
如果对低代码架构的实现细节感兴趣,可以继续关注我的后续文章,您的回复和打赏,是我继续写下去的动力
低组件架构
整个系统在低代码架构美好的演进了一段时间,但很快遇到了一些困难
首先是业务的多样性,导致了某些组件的配置爆炸式增加,有几个典型的组件有几十个开关,其自身的复杂度也需要关注了
有些子类型其功能不具有复用性,对于这种,我们扩展了类型组件的支持,也就是给这个子类型开发一个组件,其逻辑都在一起,就和正交架构的实现区别不大了
最大的问题,有些类型,其校验逻辑和联动关系非常复杂,通过配置化来实现这些联动和校验时越来越困难,维护的同学表示很难受
关于上面的问题,我思考了许久,提出了低组件架构,这个名词是我发明的,低组件架构其实是融合了正交架构和低代码架构,博取两家之长,避开两家之短
拿表单来举个例子,我们的程序其实是分成两部分的,首先是组件的渲染,也可以理解为静态UI的展示,这一部分其实是比较容易通过DSL来表示的;在UI背后还有逻辑层,包括组件的联动,校验等逻辑,这一部分逻辑存在DSL困境
其实我们写的代码,比如js,html等就是通用的DSL,既然如此不如换个思路?一个子类型包含哪些组件,组件包含哪些参数,通过低代码架构来实现;组件背后的逻辑层,通过正交架构来实现
低组件架构,融合了两种架构,避免了正交架构中的组件发散问题,同时也避免了低代码架构中的DSL爆炸问题
不过这个架构,我只是做了纸面上的推演,并未落地,纸上得来终觉浅,希望能给同学们一起启发,如果有同学有尝试,欢迎交流
总结
综上,我们介绍了多子类型系统的4种架构,其实4中架构都能实现需求,但其思想却有很大不同,下面同不同层面做个对比
首先从子类型数量和系统复杂度方面来对比下,低组件架构其实是低代码架构的一个变种,所以此处不单独列出
再来对比下,开发人员面对不同架构时的心智模型
通过上面的介绍和对比,相信同学们对4种架构的定义和区别都有了自己的认识,其实架构没有好坏之分,只有适合不适合,下面从我的认知,给大家总结下不同架构适合的不同业务场景
架构 | 适应场景 |
---|---|
耦合架构 | 无,除非你想离职了,不过害人终害己,天道好轮回 |
正交架构 | 子类型小于30个的场景,子类型之间区别很大时 |
低代码架构 | 逻辑不重,子类型较多时,子类型迭代较快 |
低组件架构 | 同上,但逻辑较重时 |
最后,希望本文的内容,可以帮助大家在多子类型业务中找到出路,感谢大家阅读,^_^
原文网址:http://yanhaijing.com/program/2021/07/22/decoupling-the-large-subtype/