Chap3 软件开发复杂度

复杂:由大量相互作用的部分组成的系统,与整个系统比起来,这些组成部分相对简单,没有中央控制,组成部分之间也没有全局性的通讯,并且组成部分的相互作用导致了复杂行为。
复杂度:

  • 理解力维度分为 Simple(简单)与 Complicated(难以理解)两个层次。
  • 预测能力维度则分为 Ordered(有序)、Complex(复杂)与 Chaotic(混沌)三个层次。

image.png
系统复杂包含 Complex 和 Complicated 两方面。

理解力

规模

当需求呈现线性增长的趋势时,为了实现这些功能,软件规模也会以近似的速度增长。由于需求不可能做到完全独立,导致出现相互影响相互依赖的关系,修改一处就会牵一发而动全身。

结构

系统属性决定结构:需要满足高性能、高并发的需求,就需要考虑在系统中引入缓存、并行处理、CDN、异步消息以及支持分区的可伸缩结构。倘若我们需要支持对海量数据的高效分析,就得考虑这些海量数据该如何分布存储,并如何有效地利用各个节点的内存与 CPU 资源执行运算。
无序设计-《两个系统的故事:现代软件神话》- Pete Goodliffe:

  • 代码没有显而易见的进入系统中的路径;
  • 不存在一致性、不存在风格、也没有统一的概念能够将不同的部分组织在一起;
  • 系统中的控制流让人觉得不舒服,无法预测;
  • 系统中有太多的“坏味道”,整个代码库散发着腐烂的气味儿,是在大热天里散发着刺激气体的一个垃圾堆;
  • 数据很少放在使用它的地方,经常引入额外的巴罗克式缓存层,目的是试图让数据停留在更方便的地方。

元素模糊不清(Unclear)、技术债(Techonical Debt)。

预测能力

变化

拒绝对变化做出理智的预测,系统的设计会变得僵化;过度设计,开发成本损失大。
第三方依赖,版本共存的依赖地狱,版本升级后回归测试。
一方面我们要尽可能地控制变化,至少要将变化产生的影响限制在较小的空间范围内;另一方面又要保证系统不会因为满足可扩展性而变得更加复杂(过度设计)。

控制复杂度

复杂成因:规模、结构、变化。

控制规模

小即是美 - KISS(Keep it Simple Stupid)。
Unix - Doug McIlroy、Elliot Pinson 和 Berk Tague - 设计哲学:

  • Make each program do one thing well. To do a new job, build a fresh rather than complicate old programs by adding new “features”.
  • Expect the output of every program to become the input to another, as yet unknown, program.

第一条原则要求一个程序只做一件事情,符合“单一职责原则”,在应对新需求时,不会直接去修改一个复杂的旧系统,而是通过添加新特性,然后对这些特性进行组合。
第二条原则,即每个程序的输入和输出都是统一的,因而形成一个统一接口(Uniform Interface),以支持程序之间的自由组合(Composability)。利用统一接口,既能够解耦每个程序,又能够组合这些程序,还提高了这些小程序的重用性,这种“统一接口”,其实就是架构一致性的体现。

保持结构清晰一致

清晰直观 - 易于理解。
Robert Martin - 关注点分离 - 整洁架构:
关注点分离:Alistair Cockburn 六边形架构(Hexagonal Architecture)、Jeffrey Palermo 洋葱架构(Onion Architecture)、James Coplien 与 Trygve Reenskaug 的 DCI 架构的共同原则。
整洁架构提出了一个可测试的模型,无需依赖于任何基础设施就可以对它进行测试,只需通过边界对象发送和接收对应的数据结构即可。它们都遵循稳定依赖原则,不对变化或易于变化的事物形成依赖。整洁架构模型让外部易变的部分依赖于更加稳定的领域模型,从而保证了核心的领域模型不会受到外部的影响。
image.png
整洁架构的目的在于识别整个架构不同视角以及不同抽象层次的关注点,并为这些关注点划分不同层次的边界,从而使得整个架构变得更为清晰,以减少不必要的耦合。要做到这一点,则需要合理地进行职责分配,良好的封装与抽象,并在约束的指导下为架构建立一致的风格,这是许多良好系统的设计特征。

拥抱变化

Kent Beck - 拥抱变化。

  • 开发:敏捷,快速迭代。
  • 架构:质量属性。

质量属性

  1. 可进化性(Evolvability)

确定设计单元的职责和协作接口,不同粒度,改变边界内的实现细节,可进化。

  1. 可扩展性(Extensibility)

变化点(业务规则、外部服务、数据格式…),封装处理,隔离变化降低耦合,基于事件、管道-过滤器,提高扩展性。

  1. 可定制性(Customizability)

提供特别的功能与服务,Fielding - 《架构风格与基于网络的软件架构设计》- 支持可定制性的风格也可能会提高简单性和可扩展性。

  • 在 SaaS 风格的系统架构中,我们常常通过引入元数据(Metadata)来支持系统的可定制。
  • 插件模式通过提供统一的插件接口,使得用户可以在系统之外按照指定接口编写插件来扩展定制化的功能。

    Chap6 DDD 应对软件复杂度

    需求:技术复杂度- 业务复杂度。

    复杂度隔离

    确定业务逻辑与技术实现的边界 - 分层架构和六边形架构。

    分层架构 - 关注点分离

    将属于业务逻辑的关注点放到领域层(Domain Layer)中,而将支撑业务逻辑的技术实现放到基础设施层(Infrastructure Layer)中。
    DDD 又引入了应用层(Application Layer):

  • 一方面它作为业务逻辑的外观(Facade),暴露了能够体现业务用例的应用服务接口;

  • 另一方面它又是业务逻辑与技术实现的粘合剂。

典型的领域驱动设计分层架构:
image.png
蓝色区域的内容与业务逻辑有关,灰色区域的内容与技术实现有关,二者泾渭分明,然后汇合在应用层。应用层确定了业务逻辑与技术实现的边界,通过直接依赖或者依赖注入(DI,Dependency Injection)的方式将二者结合起来。

六边形架构 - 内外分离

Cockburn - 内外分离 - 业务逻辑在架构的核心位置。
image.png
体现业务逻辑的应用层与领域层处于六边形架构的内核,并通过内部的六边形边界与基础设施的模块隔离开。
当我们在进行软件开发时,只要恪守架构上的六边形边界,则不会让技术实现的复杂度污染到业务逻辑,保证了领域的整洁。边界还隔离了变化产生的影响。
如果我们在领域层或应用层抽象了技术实现的接口,再通过依赖注入将控制的方向倒转,业务内核就会变得更加的稳定,不会因为技术选型或其他决策的变化而导致领域代码的修改。

领域模型 - 领域知识

领域模型 - 业务需求 - 概念、规则、概念之间的关系 - 统一语言可视化 - 封装,隐藏细节;抽象,提取共同特征。
了解整个系统的目标和范围,对系统的领域需求达成共识。

共识

沟通与协作 - 无法“发现”需求,而是要和客户一起“培育”需求,并在这个培育过程中逐渐成熟。
信息不同,背景不同 —— 原始的需求是“哈姆雷特”。
image.png
合同方式约定需求知识不可靠,易发生超出文档边界的变更 —— 理解偏差的存在:

  • 从客户那里了解到的需求,并非最终的需求。
  • 沟通有效性低,理解偏差。
  • 理解到的需求并没有揭示完整的领域知识。

通过可视化的交流形式逐渐在多个角色之间达成共识:
image.png
可视化的方式表现出来,例如,绘图、便签、编写用户故事或测试用例等都是重要的辅助手段。
image.png
看到需求之间的差异,互补不足去冗余,最终得到大家都一致认可的需求,形成统一的认知模型。

团队协作

项目之初,尤其是有客户参与的先启阶段,是最好的理解领域知识的方法。
建立初步的统一语言,在识别出主要的史诗级故事主要用户故事之后,进而识别出限界上下文,并建立系统的逻辑架构与物理架构。
image.png

  • 在期望与愿景的核心目标指导下,团队与客户才可能就问题域达成共同理解。
  • 我们需要确定项目的当前状态与未来状态,从而确定项目的业务范围。
  • 之后,就可以对需求进行分解了。
  • 在先启阶段,对需求的分析不宜过细,因此需求分解可以从史诗级(Epic)到主故事级(Master)进行逐层划分。
  • 并最终在业务范围内确定迭代开发需要的主故事列表。

迭代开发沟通关键点:
image.png
每一个功能的实现、每一行代码的编写都是围绕着用户故事开展的,它是构成领域知识的最基本单元

发开人员

当用户故事从需求分析人员传递给开发人员时,充分理解用户故事描述的需求后,将需求分析人员与测试人员叫过来,大家一起做一个极短时间的沟通与确认。我们称这一活动为“Kick Off” —— 应对“盲人摸象”。
开发人员多问需求分析人员“为什么”,探索用户故事带来的价值,理解业务逻辑与业务规则。
开发人员要与测试人员再三确认验收标准,以形成一种事实上的需求规约。

运用领域场景提炼领域知识

业务场景分析 6W 要素:Who、What、Why、Where、When、HoW
image.png
识别用户角色,分析用户特征和属性,辨别其在场景中参与的活动 —— 明确业务功能(What),该功能给用户带来的业务价值(Why)。
领域功能三层次(职责层次,Responsebilaty):

  • 业务价值(Why):业务价值体现职责的目的,提供职责,“用户履行了职责”,场景对用户才有价值。
  • 业务功能(What):支撑功能,实现业务价值。
  • 业务实现(hoW):深入分析功能,获得具体的如何业务实现。

    下订单

    下订单这一职责具有业务价值,职责分层结构:

  • 下订单

    • 验证订单有效
      • 合法用户
      • 信息完整
      • 商品库存
    • 验证业务规则计算订单总价、优惠与配送费
      • 促销规则
      • 订单总价
      • 订单优惠
      • 配送费
    • 提交订单
      • 插入订单、订单项
      • 订单状态
    • 更新购物车
      • 删除商品
    • 发送通知
      • 邮件通知付款

利用场景建模,考虑场景边界。校验库存量的业务实现需要库存接口,该功能属于下订单场景的边界之外(引入限界上下文)。
提炼的领域知识必须具备 6W 要素

  • 校验领域逻辑,缺乏部分要素 => 忽略了重要领域概念。
  • 按场景 6W 模型分析领域逻辑、提炼领域知识 => 保证领域模型完整性。

    领域场景分析方法