Skip to content

Alpha 系列产品技术方案

x

问题域

1. mindera 代码现状

  • 代码中的坏味道

    • 语法检查

      • 1.语法错误代码

      x

      • 2.声明而未使用

      x

      • 问题

        • 无法理解
        • 消耗性能
    • 无效文件

    x

    • 过多的方法中存在大量的if-else代码块

      • 一个复杂的逻辑几乎没有方法的抽象和提取,只能根据if判断条件去理解业务逻辑。这是典型的代码坏味道。

      x

      • 代码阅读体验差,无形中提高了理解成本
      • 解决方法

        • 卫语句

          • 《重构》:如果条件语句极其复杂,就应该将条件语句拆解开,然后逐个检查,并在条件为真时立刻从函数中返回,这样的单独检查通常被称之为“卫语句”(guard clauses)。
        • 策略模式

    • 过长的参数列表

      • 特征:一个方法有超过三四个的参数

      x

      • 太多参数会造成前后不一致、不易使用,而且一旦需要更多数据,就不得不修改它。
      • 在长代码块里理解难度高,容易引发 散弹式修改
      • 一个参数意味着一条依赖,容易违背单一职责原则
      • 有些参数甚至存在包含关系:违背 MECE(Mutually Exclusive Collectively Exhaustive:不重叠、不遗漏)
      • 解决

        • 保持对象完整(Preserve Whole Object) 将来自同一对象的一堆数据收集起来,并以该对象替换它们
        • 使用 引入参数对象(Introduce Parameter Object) 为它们制造出一个“参数对象”
        • 更易读,更简短的代码;重构时可能会暴露出之前未注意到的重复代码。
    • 案例:router.js

      • 违背单一职责原则

        • SRP定义:一个类应该有且一个引起它变化的原因,否则类应该被拆分。
        • 变更原因一:增加路由配置,页面入口,或修改页面入口
        • 原因二:路由前后的 Intercepter Hook,权限判断,webfunny,waterMark
        • 不同的变更原因,不同变更频率,封装在同一个代码块或组件
      • 树状结构树状表达

        • 2130 行的数组表示树状结构

        x

        • 最好从外观上能看到模块的划分和层级的拆分
        • 程序越长越难理解,违背KISS原则

          • 应该致力于代码的 可理解性;降低复杂度也意味着维护变得简单。
          • Martin Flower在《重构》中有一句经典的话:"任何一个傻瓜都能写出计算机可以理解的程序,只有写出人类容易理解的程序才是优秀的程序员。“
  • 层次与边界(代码架构)

    • 最基础切分方式: 纵向切分:按照业务领域划分边界; 横向切分:按照功能特性分层次。
    • 严格分层架构:某层只能与位于其直接下方的层发生耦合。
  • 基础组件/公共模块无法抽取

    • 组件依赖关系典型案例

    x

    • 问题在哪里

        1. 违背 ADP:无环依赖原则

        2. 模块依赖关系图中不应该出现环。

        1. 违背 DIP:依赖倒置原则

        2. 高层组件依赖不应该依赖于底层组件。

        3. 层次定义:一组策略距离系统的输入/输出越远,它所属的层次越高。
        1. Big ball of mud:大泥球代码、混沌系统

        2. 一个维护多年的遗产系统最后都会成为一个混沌系统——代码间的联系可正亦可负、可有亦可无。

        3. 系统往往缺少架构设计资料,或从未有过良好的架构分层
        4. 系统中相距很远的模块被杂乱无章的共享
        5. 重要信息被设为全局上下文,通常还会被重复定义
        6. 任何一个系统,从设计之初必定都是一个良好设计的系统,如何成现在这样:一天天。
        7. 解决方案:review & refactor,防止温水煮青蛙,别把任务排的太满。

2. alpha 生态业务模型

  • 战略与战术

    • 愿景

    x

  • 前后端整体技术架构

x

  • 平台端/SaaS端

    • 产品领域

    x

3. alpha 生态技术架构

  • 前端技术架构图

    • 全景图

    x

  • 基础设施模块(稳定)

  • 平台端 shell(封装策略/适配)
  • Saas端 shell(封装策略/适配)
  • 子应用库(变化)
  • 小程序

战略思想

业务与架构

  • 康威定律

    • Communication dictates the design。组织沟通方式会通过系统设计表达出来 。
    • 线型系统和线型组织架构间有潜在的异质同态特性。
    • 同样也适用于业务的组织架构。架构拆分的原则,首先来源于业务自身的组织架构,软件架构要保持和业务组织架构的映射关系。

    x

  • 业务进化与架构

    • 业务相当于基因,而架构呈树状延展则相当于细胞的分裂。就好比每一个人,都是从一个细胞逐渐分裂出来的。一个细胞最终分裂成什么,起决定作用的不是分裂本身,而是它的基因。基因决定了细胞最终会分裂生长成什么样的一个生命。

      • 比如一棵树从种子长成小树苗,再长成参天大树,这不叫进化,这叫生长。长成什么树是由树的基因决定的,不是架构。因为不管怎么拆分,业务的目标,也就是基因没有任何变化。
      • 如果一个企业的业务由制造商变成电商,这个时候业务就发生进化了。因为基因变了,业务已经完全不一样了,整棵架构树的含义就变了。该企业的软件架构就需要重新设计,而不能在原有的架构上修改,也就不存在架构的进化。
    • 只有业务才会进化,架构是支撑业务长大的。业务的核心生命周期相当于架构树的主干,主干没有变化,则说明没有变成一棵不同品种的树,因此只有架构的拆分不能叫作进化。

  • 架构分层和树的关系

    • 我们每次谈到架构,都需要谈论树状结构,那么树状结构有什么好处呢?树状结构特别适合增长。

      • 树状的增长,成本方面最低、沟通的路径也没有显著的增长,带来的效果最好。树的结构也保证了分支的能量汇集到树干,保障了整棵树的生长。而架构恰恰是为了应对增长的,自然而然架构都是树状的。
    • 分层实际上是架构树状拆分的结果,所以当我们采用分层的时候,内心先要有树的概念。如果我们直接采用分层的方式设计,最后就会违背树的原则,导致很多复杂的问题发生,从而影响到系统的长大,例如跨层访问形成了图,这就违反了树的原则。

    • 拆分始终是围绕着业务的核心生命周期进行的,结果只有一个:无论如何拆分,它依然是树。
  • 业务、技术与架构三者关系

    • 业务是核心,技术是解决业务问题的工作,而架构是让业务长大的组织方法。
    • 架构需要用技术来实现拆分,技术需要架构来合理组织,以提升效率。软件和业务最终是要合体的。

稳定与变化

  • 软件设计复杂性的根本原因: 变化。
  • 面向对象设计的第一原则: 封装变化点。
  • 找到系统中稳定与变化的分离点,封装变化点;隔离变化,将变化带来的影响降到最小。
  • 可变性的隔离技法:应将大部分逻辑归于不可变组件,可变组件逻辑越少越好。(函数式编程的理念)
  • 使用设计模式

    • 现代软件设计的特征是 “需求的频繁变化”。
    • 设计模式的要点是 “寻找变化点,然后在变化点处使用设计模式 ,从而更好应对需求变化”。

组件聚合原则

  • REP/CPP/CRP

x

  • CCP:共同闭包原则(维护)

    • 同时、且由于相同原因 而修改的 类,放在一个组件中;
    • 单一指责原则(SRP)的再度阐述
  • CRP:共同复用原则(拆分)

    • 不要强迫一个组件的用户依赖他们不需要的东西。
    • 为避免不必要发布而切分。
    • 接口隔离原则(ISP)的一个普适版:小而完备。

划分边界

  • 软件架构是一门划分边界的艺术。

    • 应在何时,何处画线
    • 如何定义系统边界:确定输入 & 输出
  • 构建一道防火墙;墙两侧组件应该以不同原因、不同速率变更。

  • 一个系统中通常会同时包含:高通信量、低延迟的 本地架构边界 ,和低通信量、高延迟的 服务架构边界 。
  • 邪恶的单体结构:单体结构中,组件间的交互一般通过普通函数,迅速而廉价 ;意味着跨源码层次解耦边界的通信会很频繁。

战术实践

微前端

  • 解决划分边界问题(组件通信)

    • 邪恶的单体架构
    • 源码层次的解耦:函数和数据
    • 部署(组件)层次的解耦:动态链接库
    • 服务层次的解耦:系统架构中最强的边界形式,微服务/微前端

      • 通信速度慢,通信次数少
  • 其他原因(紧耦合 -> 松耦合)

    • 增量更新(时间)
    • 代码库解构(复杂度)
    • 独立发布/部署(风险)
    • 团队自主:每人维护自己的应用(责任,自治)

重构

  • 公共模块拆分成子应用,运行时集成

x

  • 不同上下文,不同策略
  • 重构技法

    • 将对经常变化的策略的依赖推迟,保证主要流程运行的稳定性。

    x

    • 静态 -> 动态
    • 早绑定 -> 晚绑定
    • 紧耦合 -> 松耦合
    • 编译时依赖 -> 运行时依赖

策略模式

  • 动机

    • 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂; 而且有时候支持不使用的算法也是一个性能负担
    • 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
  • 定义

    • 《设计模式》GoF:定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
  • UML图

x

  • 代码结构

x

  • 思考要点

    • Strategy 及其子类为组件提供了一系列可重用的算法,从而可以使得类型在 运行时 方便的根据需要在各个算法之间进行切换。
    • Strategy模式提供了用判断语句以外的另一种选择, 消除条件判断语句 ,就是在解耦合;含有许多判断条件判断语句的代码通常需要Strategy 模式(代码中的 bad smell:繁多的if....else....)
    • 对扩展开放,对修改关闭

遗留系统问题

  • 不好的案例

x

  • 解决方案(虽然无法从根本上解决问题):从无法进化问题 -> 遗留系统问题

x

  • 遗留系统的集成会对设计中的系统造成风险,面对混沌的遗留系统,最好的方法从根本上解决的方案是推倒重建;但通常因时间/技术/政治等原因,所以需要用到:绞杀者模式和防腐层模式。
  • 绞杀者模式

    • 如何保证在运行时,新旧系统各自不受影响;如何平滑切换。

    x

    • 逐步用新模块或组件替代特定功能段,增量迁移旧系统。随着旧系统功能的更换,新系统最终取代所有旧系统,扼杀并停用旧系统。
  • 防腐层模式

    • 防腐层(Anti-Corruption Layer)一种高度防御性的策略,可以将新的系统模型与腐败隔离开来。
    • 防腐层结合了一组模式:外观facade模式和适配器模式。
    • 流程

    x

设计原则验证

DRY

  • 尽量在项目中减少重复的代码行,重复的方法,重复的模块。
  • 其实,许多设计原则和模式最本质的思想都是在消除重复。我们常提起的 重用性可维护性 其实是基于减少重复这一思想。
  • 使其每一个部件都是职责明确的并且可重用的。

高内聚/低耦合

  • 应用之间形成隔离,保障了共享模块独立的发展空间,各个共享模块既互相关联,同时又模块独立。
  • 架构的域之间低耦合、高内聚。如果隔离做得好,没有业务之间的复杂交错,所以各个业务领域发展创新不受限。
  • 标准:对外界系统要透明、简单、易理解,与外部系统的接口要简明、扼要、灵活。 内部模块高度聚合,粒度越细越不可拆解。
  • 领域思维

    • 自我履行
    • 最小完备
    • 稳定空间
    • 独立进化

封装变化点

  • 审视&重视依赖关系,理清变化点和隔离点。
  • 隔离变化,较少变化的影响。
  • 按照变化速率/变化原因拆分模块或应用
  • 设计模式/面向对象的松耦合设计并不是将变化消灭掉,而是将变化赶到一个局部的地方。
  • eg:变化是一只猫 将猫关在笼子里,要比让猫在房子里跳来跳去要好得多。

No Silverbullllet

  • 传说里最可怕的妖怪是人狼,因为随时可能出乎意料从熟悉的面孔变成可怕的怪物。
  • 为对付人狼,要找到消灭他们的银色子弹。
  • 软件项目也如此,简单明了东西(产品视角),确有可能变成一个落后进度、超出预算、存在大量缺陷的怪物。
  • 没有银色子弹

    • essense 软件活动根本任务:打造构成抽象软件实体的复杂概念结构。
    • accident 次要任务:使用编程语言表达这些抽象实体,在空间和时间的限制下将他们映射机器语言(对概念的表达和实现)。
  • 良好的设计是演化的结果。