写点什么

编码和设计思考

作者:Bingo
  • 2024-04-14
    广东
  • 本文字数:5081 字

    阅读完需:约 17 分钟

什么是好代码

“成功的奥秘在于目标的坚定” -- 迪斯雷利


这个道理也适用于编写代码,只有理解了写代码的目标,才能写出好的代码。本质上来看,代码最终都是为了交付功能,功能可以是业务层面的,也可以是技术层面的,最终都是服务于某项功能。

代码不是目的而是一种手段,是为了完成业务功能。由于项目的发展和组织的变化,代码量级、开发人员、功能的复杂度都不断增长,需要更多人去协同完成项目,好代码是用来保障团队间的高效协作,同时也面向业务未来。

 

写代码从来不是一个人一个时刻的事情,它有多人协作、面向未来的等诸多特性。因此,从这几个维度来看,好代码应该具备以下特性:

  • 职责清晰】:首先,作者对代码的设计和实现思路是清晰的;其次,代码的模块分工、外部依赖、异常处理等等都是有明确的职责和分工;

  • 【功能正确】:代码实现是符合功能需求的,不仅仅符合表面的需求,更应该挖掘需求的本质;同时不仅仅只满足现在的需求,也要考虑需求未来的变化;

  • 实现优雅】:代码实现是面向抽象的过程(OOP),实现上需要采用优秀的方法,从而保证代码职责清晰、面向扩展,后续增加新功能都可以优雅的实现;

  • 高效安全】:代码运行是稳定的、高效的,给到用户的体验是友好的;实现是安全的,不会被恶意的入侵、不会导致敏感信息泄露等等。


如何写好代码

基于对如何写好代码的理解,我们可以从思维、方法、实现三个层次去思考如何写好代码。


  • 思维。设计思路强调高内聚、低耦合原则,高内聚为了降低模块的复杂性,从而聚焦核心功能;低耦合是了减少对其他模块的依赖,从而形成清晰的连接。职责清晰意味着代码功能明确、专注收敛,可复用性和可维护性极高;

  • 方法。设计方法强调代码能够清晰的划分职责、兼容未来的功能扩展,在设计过程中需要采用一些成熟的方法去划分职责、划分领域、抽象对象;

  • 实现。代码实现强调可复用性、可维护性,晦涩的代码就会出现重复造轮子、逻辑扩散的问题,容易理解的代码才会被他人正确的理解、复用、维护。

本文主要从思维、方法、实现三个方面分享笔者关于编码和设计的一些总结思考。


思维

核心思想

设计的核心理念都是高内聚、低耦合,代码、组件和服务都是如此。

 

设计原则

  • 组件原则(RCC)

  • 复用/发布等同原则(REP):软件复用的最小粒度等同于其发布的最小粒度。组件中的类与模块必须是紧密相关的。

  • 共同封闭原则(CCP):互相影响的类应该放在一起。

  • 共同复用原则(CRP):不要强迫一个组件的用户依赖他们不需要的东西。

  • 组件依赖(ASS)

  • 无依赖环原则(ADP):组件依赖关系图中不应该出现环。

  • 稳定依赖原则(SDP):依赖关系必须要指向更稳定的方向。

  • 稳定抽象原则(SAP):一个组件的抽象化程度应该与其稳定性保持一致

  • 编码设计(SOLID)

  • 单一职责(SRP):任何一个软件模块都应该只对某一类行为负责。

  • 优势:

  • 定位明确。不会导致错用、误用引发程序问题;

  • 易于组合。通过外观、组合等方式,可以灵活的组合单一职责的模块,构建更丰富的功能。

  • 不重复原则(DRY):每一个知识必须是单一、明确、权威表达的,不存在两个模块表达相同的知识。

  • 优势:

  • 减少代码耦合;

  • 防止代码重复,提高可读性、可维护性。

  • 开放-关闭原则(OCP):设计良好的计算机软件应该易于扩展,同时抗拒修改。

  • 优势:

  • 不会导致依赖问题。满足 OCP 软件不会因为被修改,而导致依赖放未知的问题。

  • 里氏替换原则(LSP):子类型必须能够替换基类。

  • 优势:

  • 简化处理逻辑。无须应该使用子类而作定制化的应对措施。

  • 接口隔离原则(ISP):任何层次的软件都不依赖它不需要的方法。

  • 优势:

  • 不会因为无关依赖改变而受影响。

  • 依赖倒置原则(DIP):高层次不依赖低层次,依赖抽象不依赖实现。

  • 优势:

  • 更为稳定。不会因为实现替换而需要修改代码。


方法

领域

领域设计核心思想是通过领域驱动设计方法确定领域模型、确定业务和应用的边界,保证业务模型和代码模型的一致性。


战略和战术设计

领域设计包括战略和战术设计两部分:

  • 战略设计。从从业务视角出发,建立领域模型,划分领域边界,建立通用的语言的限界上下文,限界上下文可以作为微服务的参考边界;

  • 战术设计。从技术的视角出发,侧重领域模型的实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码设计和实现。

战略设计

从从业务视角出发,建立领域模型,划分领域边界,建立通用的语言的限界上下文。


领域

定义:领域是指一种特定的范围或者区域,即存在范围边界,领域就是这个边界内要解决的业务问题域。

领域的核心思想就是将问题逐级细化,来降低业务理解和系统实现的复杂度。

领域可以划分为核心域、支撑域和通用域三类。

  • 核心域。整个业务领域的核心部分,是业务成功的主要因素;

  • 通用域。核心域一般会被拆分多个子域,如果一个子域被用于整个系统,那么这个子域就是通用;

  • 支撑域。对业务的某些功能起着重要作用,但不是核心域的部分,那就是支撑域。


问题域和解决方案中的领域:

  • 问题域是领域的一部分,对问题空间的开发将产生一个核心的域。对问题的评估应该同时考虑已有子域和额外所需子域。因此,问题空间是核心域和额外所需子域;

  • 解决方案空间包括一个或多个限界上下文,即一组特定的软件模型。这是因为限界上下文即是一组特定的解决方案,它通过软件的形式来实现解决方案。

 

通用语言

定义:通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。

通用语言解决了交流沟通的障碍问题,是领域专家和开发人员能够协同合作,从而表达业务表达的准确性。

 

限界上下文

限界上下文简单描述就是特定语境的上下文,它是一个显示的边界,领域模型便存在于边界之内。

限界上下文定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。

限界上下文和微服务关系。理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。

 

上下文视图

识别在项目中起作用的每个模型,并定义其 Bounded Context,为每个 Bounded Context 命名,并把名称添加到 Ubiquitous Language 中。

Context Map 是 Bounded Context 的全局视图,描述各自的边界和关联。

限界上下文映射关系包括以下方式:

1.  共享内核(Shared Kernel)。抽象出多个团队共享的子集;

2.  C/S 关系(Consumer-Supplier Development)。上下文间有组织的上下文依赖关系;

3.  遵循者(Conformist)。下游上下文只能遵循上游上下文;

4.  防腐层(Anticorruption Layer)。上下文通过一个适配层进行交互;

5.  开放主机服务(Open Host Service)。定义一个协议让其他上下文对本上下文进行访问;


战术设计

从技术的视角出发,侧重领域模型的实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码设计和实现。


关联

模型中每个可遍历的关联,软件中都要有同样属性的机制,关联则描述了实体和值之间的关系。

限定关联三种方法:

  • 规定一个遍历方向;

  • 添加一个限定符,以便有效地减少多重关联;

  • 消除不必要的关联。


Entity

定义:拥有唯一标识符、拥有生命周期的对象的对象。

实体不是通过它们的属性定义,而是通过连续性和标识定义的,并集中关注生命周期的连续性和标识。由于实体有较高的维护成本,如无必要勿增实体。


Value Object

定义:当我们只关心一个模型元素的属性时,没有生命周期的,应把它归类为 Value Object。

Value Object 应该是不可变的,不要为它分配任何标识,由于 Value Object 是不可变队形,可以降低系统的复杂度。


Specification

业务规则通常不适合作为 Entity 或 Value Object 的职责,而且规则的变化和组合也会掩盖领域对象的基本含义。

为特殊目的创建谓词形式的显示 Value Object。Specification 就是个谓词,可用来确定对象是否满足某些标准。Specification 的主要目的包括以下部分:

  • 验证对象,检查它能否满足某些需求或者是否已经为实现某个目标做好了准备;

  • 从集合中选择一个对象;

  • 指定在创建新对象时必须满足某种需求。


 Aggregate

定义:Aggregate 是一组相关对象的统一的整体,实体和值对象协同提供服务,我们把它作为数据修改的单元。

特征:

  • Aggregate 包含一个根 Enitity 和边界,并通过根 Entity 来控制对边界内其他对象的访问;

  • 根 Entity 具有全局标识,最终负责检查固定规则;

  • Aggregate 外部对象不能引用除根 Entity 之外的其他内部对象;

  • Aggregate 内部对象可以引用其他 Aggregate 的根 Entity;

  • 删除必须一次删除 Aggregate 边界内的所有对象。


Factory

定义:用于复杂的 Entity 或者 Aggregate 创建。

当创建一个对象或创建整个 Aggregate 时,如果创建工作复杂,或者暴露了过多的内部结构,则可以使用 Factory 进行封装。


Repository

定义:Repository 负责对象的存储和访问操作,之定义操作的接口,实现在 infrastructure 层实现。


Service

定义:当领域中的某个重要的过程或者转换操作不是 Entity 或者 Value Object 的自然职责时,应该在模型中添加一个作为独立接口的操作,并声明为 SERVICE。

Servie 分类:

  • 应用层 Service:负责组合领域层 Service,并且可以直接使用基础设施层;

  • 领域层 Service:负责 Entity 和 Value Object 之间的领域逻辑。


Module

Module 从更大的角度描述了领域,选择能够描述系统的 MODULE,并使之包含一个内聚的概念集合。


建模方法

用例分析法

思路:通过收集实例,进而抽象实体、确定关联关系建模。

主要流程包括以下:

  • 用例收集。收集完整的用例,包括角色和想要达到的目的;

  • 抽象实体。提取用例的中的名词或者概念作为实体的候选;

  • 确定关联关系。提取实体或者概念之间的操作来提取关联关系;

  • 建模完成形成团队统一语言。


四色建模法

思路:通过逐步划分业务流程、领域骨干、领域模型,不断地划分 MI(事件)、PPT(实体)、Role(角色)等核心模型对象,最终构建出理想的领域模型。


四色建模发展自 Peter Coad 的《彩色 UML 建模》,旨在把所有的模型对象抽象为四种,并在模型图里用四种不同的颜色标记出来。主要包括四部分:


规则:MI(事件)不能与 PPT(事物)直接打交道,必须通过 ROLE(角色)来打交道。每个淘宝用户可以扮演两种角色,买家和卖家,本例中我们只使用了买家这个角色,因为只有买家才下订单,订单只能通过买家与用户关联,而不能直接关联。

主要流程包括以下:

  • 抽象业务流程。使用 MI、PPT、Role 替换业务流程中的对应概念;

  • 构建模型骨干。根据业务流程,抽出 MI 名词和 PPT 动作,形成模型骨干;

  • 增加领域模型角色。在模型骨干的基础上,添加 Role 角色;

  • 增加领域模型描述。在上一步的基础上,完善领域描述;

  • 提取 ER 图。根据领域模型骨干,提取实体、属性和关联关系,形成 ER 图。


事件风暴法

思路:根据业务流程拆分出一系列的事件和命令,再通过将事件和命令进行聚合归类,进而形成领域实体、聚合、聚合根以及限界上下文。


事件风暴通过头脑风暴的形式,罗列出领域中所有的领域事件,整合之后形成最终的领域事件集合,然后对每一个事件,标注出导致该事件的命令,再为每一个事件标注出命令发起方的角色。

命令可以是用户发起,也可以是第三方系统调用或者定时器触发等,最后对事件进行分类,整理出实体、聚合、聚合根以及限界上下文。

主要流程包括以下:

  • 产品愿景。产品愿景的主要目的是对产品顶层价值的设计,使产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向;

  • 业务场景分析。根据业务流程或用户旅程,采用用例和场景分析,探索领域中的典型场景,找出领域事件、实体和命令等领域对象,支撑领域建模;

  • 领域建模。根据场景分析过程中产生的领域对象,比如命令、事件等之间关系,找出产生命令的实体,分析实体之间的依赖关系组成聚合,为聚合划定限界上下文,建立领域模型以及模型之间的依赖;

  • 微服务的拆分和设计。


应用集成

领域设计架构核心思路:领域是核心,接口和基础设施是辅助,领域尽可能不受外部因素影响。架构整体上可以分为接口层、应用层、领域层和基础设施层,领域层为核心。常见的架构模式包括以下集中:分层架构、洋葱架构、COLA 架构。


分层架构

分层架构存在的问题:

  • 基础层对领域内聚性有较大影响。基础层位于领域层的下方,会导致领域层过多依赖基础设施的实现,对核心领域实现的内聚产生影响;

  • 依赖倒置引起的分层结构丢失。为了解决对领域层的内聚影响,采用 DIP 的方式,领域层不再依赖具体的基础层,导致基础层处于最上层,没有原先的分层结构。


洋葱架构

洋葱架构应用分层各个模块的定位和依赖关系如下:


COLA 架构

COLA 架构应用分层和各个模块的定位和依赖关系如下:


推荐方式

总结来看,洋葱架构或者 COLA 架构过于复杂,实际上外围的 infrastructure 基本不会变更,因此,推荐采用分层架构。


模式

思路:模式是一些优秀思路的总结,有利于日常编码的快速落地。


创建模式


结构模式


行为模式


实现


未完待续!!!

用户头像

Bingo

关注

提升认知 2020-12-07 加入

还未添加个人简介

评论

发布
暂无评论
编码和设计思考_设计模式_Bingo_InfoQ写作社区