Alpha 系列产品技术方案¶
问题域¶
1. mindera 代码现状¶
-
代码中的坏味道
-
过多的方法中存在大量的if-else代码块
- 一个复杂的逻辑几乎没有方法的抽象和提取,只能根据if判断条件去理解业务逻辑。这是典型的代码坏味道。
- 代码阅读体验差,无形中提高了理解成本
-
解决方法
-
卫语句
- 《重构》:如果条件语句极其复杂,就应该将条件语句拆解开,然后逐个检查,并在条件为真时立刻从函数中返回,这样的单独检查通常被称之为“卫语句”(guard clauses)。
-
策略模式
-
-
过长的参数列表
- 特征:一个方法有超过三四个的参数
- 太多参数会造成前后不一致、不易使用,而且一旦需要更多数据,就不得不修改它。
- 在长代码块里理解难度高,容易引发 散弹式修改
- 一个参数意味着一条依赖,容易违背单一职责原则
- 有些参数甚至存在包含关系:违背 MECE(Mutually Exclusive Collectively Exhaustive:不重叠、不遗漏)
-
解决
- 保持对象完整(Preserve Whole Object) 将来自同一对象的一堆数据收集起来,并以该对象替换它们
- 使用 引入参数对象(Introduce Parameter Object) 为它们制造出一个“参数对象”
- 更易读,更简短的代码;重构时可能会暴露出之前未注意到的重复代码。
-
案例:router.js
-
违背单一职责原则
- SRP定义:一个类应该有且一个引起它变化的原因,否则类应该被拆分。
- 变更原因一:增加路由配置,页面入口,或修改页面入口
- 原因二:路由前后的 Intercepter Hook,权限判断,webfunny,waterMark
- 不同的变更原因,不同变更频率,封装在同一个代码块或组件
-
树状结构树状表达
- 2130 行的数组表示树状结构
- 最好从外观上能看到模块的划分和层级的拆分
-
程序越长越难理解,违背KISS原则
- 应该致力于代码的 可理解性;降低复杂度也意味着维护变得简单。
- Martin Flower在《重构》中有一句经典的话:"任何一个傻瓜都能写出计算机可以理解的程序,只有写出人类容易理解的程序才是优秀的程序员。“
-
-
-
层次与边界(代码架构)
- 最基础切分方式: 纵向切分:按照业务领域划分边界; 横向切分:按照功能特性分层次。
- 严格分层架构:某层只能与位于其直接下方的层发生耦合。
-
基础组件/公共模块无法抽取
- 组件依赖关系典型案例
-
问题在哪里
-
-
违背 ADP:无环依赖原则
-
模块依赖关系图中不应该出现环。
-
-
-
违背 DIP:依赖倒置原则
-
高层组件依赖不应该依赖于底层组件。
- 层次定义:一组策略距离系统的输入/输出越远,它所属的层次越高。
-
-
-
Big ball of mud:大泥球代码、混沌系统
-
一个维护多年的遗产系统最后都会成为一个混沌系统——代码间的联系可正亦可负、可有亦可无。
- 系统往往缺少架构设计资料,或从未有过良好的架构分层
- 系统中相距很远的模块被杂乱无章的共享
- 重要信息被设为全局上下文,通常还会被重复定义
- 任何一个系统,从设计之初必定都是一个良好设计的系统,如何成现在这样:一天天。
- 解决方案:review & refactor,防止温水煮青蛙,别把任务排的太满。
-
-
2. alpha 生态业务模型¶
3. alpha 生态技术架构¶
战略思想¶
业务与架构¶
-
康威定律
- Communication dictates the design。组织沟通方式会通过系统设计表达出来 。
- 线型系统和线型组织架构间有潜在的异质同态特性。
- 同样也适用于业务的组织架构。架构拆分的原则,首先来源于业务自身的组织架构,软件架构要保持和业务组织架构的映射关系。
-
业务进化与架构
-
业务相当于基因,而架构呈树状延展则相当于细胞的分裂。就好比每一个人,都是从一个细胞逐渐分裂出来的。一个细胞最终分裂成什么,起决定作用的不是分裂本身,而是它的基因。基因决定了细胞最终会分裂生长成什么样的一个生命。
- 比如一棵树从种子长成小树苗,再长成参天大树,这不叫进化,这叫生长。长成什么树是由树的基因决定的,不是架构。因为不管怎么拆分,业务的目标,也就是基因没有任何变化。
- 如果一个企业的业务由制造商变成电商,这个时候业务就发生进化了。因为基因变了,业务已经完全不一样了,整棵架构树的含义就变了。该企业的软件架构就需要重新设计,而不能在原有的架构上修改,也就不存在架构的进化。
-
只有业务才会进化,架构是支撑业务长大的。业务的核心生命周期相当于架构树的主干,主干没有变化,则说明没有变成一棵不同品种的树,因此只有架构的拆分不能叫作进化。
-
-
架构分层和树的关系
-
我们每次谈到架构,都需要谈论树状结构,那么树状结构有什么好处呢?树状结构特别适合增长。
- 树状的增长,成本方面最低、沟通的路径也没有显著的增长,带来的效果最好。树的结构也保证了分支的能量汇集到树干,保障了整棵树的生长。而架构恰恰是为了应对增长的,自然而然架构都是树状的。
-
分层实际上是架构树状拆分的结果,所以当我们采用分层的时候,内心先要有树的概念。如果我们直接采用分层的方式设计,最后就会违背树的原则,导致很多复杂的问题发生,从而影响到系统的长大,例如跨层访问形成了图,这就违反了树的原则。
- 拆分始终是围绕着业务的核心生命周期进行的,结果只有一个:无论如何拆分,它依然是树。
-
-
业务、技术与架构三者关系
- 业务是核心,技术是解决业务问题的工作,而架构是让业务长大的组织方法。
- 架构需要用技术来实现拆分,技术需要架构来合理组织,以提升效率。软件和业务最终是要合体的。
稳定与变化¶
- 软件设计复杂性的根本原因: 变化。
- 面向对象设计的第一原则: 封装变化点。
- 找到系统中稳定与变化的分离点,封装变化点;隔离变化,将变化带来的影响降到最小。
- 可变性的隔离技法:应将大部分逻辑归于不可变组件,可变组件逻辑越少越好。(函数式编程的理念)
-
使用设计模式
- 现代软件设计的特征是 “需求的频繁变化”。
- 设计模式的要点是 “寻找变化点,然后在变化点处使用设计模式 ,从而更好应对需求变化”。
组件聚合原则¶
- REP/CPP/CRP
-
CCP:共同闭包原则(维护)
- 同时、且由于相同原因 而修改的 类,放在一个组件中;
- 单一指责原则(SRP)的再度阐述
-
CRP:共同复用原则(拆分)
- 不要强迫一个组件的用户依赖他们不需要的东西。
- 为避免不必要发布而切分。
- 接口隔离原则(ISP)的一个普适版:小而完备。
划分边界¶
-
软件架构是一门划分边界的艺术。
- 应在何时,何处画线
- 如何定义系统边界:确定输入 & 输出
-
构建一道防火墙;墙两侧组件应该以不同原因、不同速率变更。
- 一个系统中通常会同时包含:高通信量、低延迟的 本地架构边界 ,和低通信量、高延迟的 服务架构边界 。
- 邪恶的单体结构:单体结构中,组件间的交互一般通过普通函数,迅速而廉价 ;意味着跨源码层次解耦边界的通信会很频繁。
战术实践¶
微前端¶
-
解决划分边界问题(组件通信)
- 邪恶的单体架构
- 源码层次的解耦:函数和数据
- 部署(组件)层次的解耦:动态链接库
-
服务层次的解耦:系统架构中最强的边界形式,微服务/微前端
- 通信速度慢,通信次数少
-
其他原因(紧耦合 -> 松耦合)
- 增量更新(时间)
- 代码库解构(复杂度)
- 独立发布/部署(风险)
- 团队自主:每人维护自己的应用(责任,自治)
重构¶
- 公共模块拆分成子应用,运行时集成
策略模式¶
-
动机
- 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂; 而且有时候支持不使用的算法也是一个性能负担
- 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
-
定义
- 《设计模式》GoF:定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
-
UML图
- 代码结构
-
思考要点
- Strategy 及其子类为组件提供了一系列可重用的算法,从而可以使得类型在 运行时 方便的根据需要在各个算法之间进行切换。
- Strategy模式提供了用判断语句以外的另一种选择, 消除条件判断语句 ,就是在解耦合;含有许多判断条件判断语句的代码通常需要Strategy 模式(代码中的 bad smell:繁多的if....else....)
- 对扩展开放,对修改关闭
遗留系统问题¶
- 不好的案例
- 解决方案(虽然无法从根本上解决问题):从无法进化问题 -> 遗留系统问题
- 遗留系统的集成会对设计中的系统造成风险,面对混沌的遗留系统,最好的方法从根本上解决的方案是推倒重建;但通常因时间/技术/政治等原因,所以需要用到:绞杀者模式和防腐层模式。
-
绞杀者模式
- 如何保证在运行时,新旧系统各自不受影响;如何平滑切换。
- 逐步用新模块或组件替代特定功能段,增量迁移旧系统。随着旧系统功能的更换,新系统最终取代所有旧系统,扼杀并停用旧系统。
-
防腐层模式
- 防腐层(Anti-Corruption Layer)一种高度防御性的策略,可以将新的系统模型与腐败隔离开来。
- 防腐层结合了一组模式:外观facade模式和适配器模式。
- 流程
设计原则验证¶
DRY¶
- 尽量在项目中减少重复的代码行,重复的方法,重复的模块。
- 其实,许多设计原则和模式最本质的思想都是在消除重复。我们常提起的 重用性 和 可维护性 其实是基于减少重复这一思想。
- 使其每一个部件都是职责明确的并且可重用的。
高内聚/低耦合¶
- 应用之间形成隔离,保障了共享模块独立的发展空间,各个共享模块既互相关联,同时又模块独立。
- 架构的域之间低耦合、高内聚。如果隔离做得好,没有业务之间的复杂交错,所以各个业务领域发展创新不受限。
- 标准:对外界系统要透明、简单、易理解,与外部系统的接口要简明、扼要、灵活。 内部模块高度聚合,粒度越细越不可拆解。
-
领域思维
- 自我履行
- 最小完备
- 稳定空间
- 独立进化
封装变化点¶
- 审视&重视依赖关系,理清变化点和隔离点。
- 隔离变化,较少变化的影响。
- 按照变化速率/变化原因拆分模块或应用
- 设计模式/面向对象的松耦合设计并不是将变化消灭掉,而是将变化赶到一个局部的地方。
- eg:变化是一只猫 将猫关在笼子里,要比让猫在房子里跳来跳去要好得多。
No Silverbullllet¶
- 传说里最可怕的妖怪是人狼,因为随时可能出乎意料从熟悉的面孔变成可怕的怪物。
- 为对付人狼,要找到消灭他们的银色子弹。
- 软件项目也如此,简单明了东西(产品视角),确有可能变成一个落后进度、超出预算、存在大量缺陷的怪物。
-
没有银色子弹
- essense 软件活动根本任务:打造构成抽象软件实体的复杂概念结构。
- accident 次要任务:使用编程语言表达这些抽象实体,在空间和时间的限制下将他们映射机器语言(对概念的表达和实现)。
-
良好的设计是演化的结果。