1、命名
1.3 有意义的命名
1.3.1 变量名:如果一个变量名需要注释来补充说明,那么很可能说明命名就有问题;
魔术数用常量定义的好处是更易于搜索。
1.3.2 函数名:函数名要具体,空泛的命名没有意义;
函数的命名要体现做什么,而不是怎么做,合理的命名可以是你省掉记住“出栈”的脑力步骤。(例:getLastestEmployee优于popRecord)
1.3.3 类名:类可以分为实体类和辅助类;
实体类:承载了核心业务数据和核心业务逻辑,其命名要充分体现业务语义,并在团队内达成共识;
辅助类:辅佐实体类一起完成业务逻辑,其命名要能通过后缀来体现功能。(原文不建议使用Helper、Util之类的后缀,但是我认为应视情况而定)
1.3.4 包名:包代表了一组有关系的类的集合,起到分类组合和命名空间的作用;
1.3.5 模块名:在COLA架构规范中定义了xxx-controller、xxx-app、xxx-domain、xxx-Infrastructure(定义成小写可能更统一?) 4个标准模块。
1.4 保持一致性
1.4.1 每个概念一个词
方法名的约定
CRUD操作 | 方法名约定 |
---|---|
新增 | create |
添加 | add |
删除 | remove |
修改 | update |
查询(单个结果) | get |
查询(多个结果) | list |
分页查询 | page |
统计 | count |
1.4.3 后置限定词
避免使用Num,可以使用Total表示总数,Id表示编号。
1.5 自明的代码
1.5.1 中间变量
适当使用中间变量可以使语义更透明(此书中与《重构》一书中意见不同,但是我倾向于两种的折中,即适当使用能更好的帮助理解代码,而不是全部使用、也不是都不使用)
1.5.3 小心注释
1)不要复述功能;2)要解释背后的意图
2、规范
2.3 代码规范
2.3.2 空行规范
每个空白行都是一条线索,提示你下一组代码表示的是不同的概念或功能。
2.3.4 日志规范
1)ERROR级别:表示不能自己恢复的错误,需要立即被关注和解决。对于ERROR,我们不仅要打印线程和堆栈,最好打印出一定的上下文(例:链路TraceId、用户Id、订单Id、外部传来的关键数据);
2)WARN级别:对于可预知的业务问题,最好不要用ERROR输出日志,以免污染报警系统(例:参数校验不通过、没有访问权限等业务异常);在短时间内产生过多WARN日志,也是一种系统不健康的表现,如果是产品设计上有缺陷导致用户频繁出现操作卡点,可以考虑做一下流程或者产品上的优化;
3)INFO级别:用于记录系统的基本运行过程和运行状态(例:系统状态变化的日志、业务流程的核心处理、关键动作和业务流程化的状态变化);
4)DEBUG是输出调试信息,通常在开发和预发环境下,DEBUG日志会打开;只有当线上出现bug或者棘手的问题时,才可以动态地开启DEBUG。
2.3.5 异常规范
1)异常处理
在业务系统中设定两个异常,分别是BizException(业务异常-WARN级别)和SysException(系统异常-ERROR级别),而且这两个异常都应该是Unchecked Execption,因为Checked Exception破坏了开闭原则。千万不要在业务处理内部到处使用try/catch打印错误日志。
2)错误码
编号错误码:对于不同的错误波段,一定要预留足够的码号;
显性化错误码:显性化的错误码具有更强的灵活性,适合敏捷开发。(例:可以将错误码定义成3个部分:类型_场景_自定义标识符,并做一个约定,P代表参数异常、B代表业务异常、S代表系统异常。
2.6 防止破窗
3、函数
3.3 封装判断
如果没有上下文,if和while语句中的布尔逻辑就难以理解。如果把解释条件意图作为函数抽离出来,用函数名把判断条件的语句显性化地表达出来,就能立即提升代码的可读性和可理解性。
3.4 函数参数 3.5 短小的函数
作者的建议边界是:3个和20行
3.7 精简辅助代码
3.7.1 优化判空
Optional.ofNullable
3.7.2 优化缓存判断
此处我认为作者想表达的是利用注解替换铅板代码
实际案例:利用注解实现productId校验
3.8 组合函数模式 3.9 SLAP
组合函数要求将一个大函数拆成多个子函数的组合,而抽象层次一致性(Single Level of Abstraction Principle, SLAP)要求函数体中的内容必须在同一个抽象层次上,如果高层次抽象和底层细节杂糅在一起,就会显得凌乱,难以理解。
4、设计原则
4.1 SOLID概览
SRP、OCP、LSP、ISP、DIP
4.2 SRP(单一职责原则)
衡量标准是模块是否只有一个被修改的原因。
4.3 OCP(开闭原则)
软件实体应该对扩展开放,对修改关闭。在面向对象设计中,我们通常通过继承和多态来实现OCP;还可以通过设计模式来实现,如:装饰者模式、策略模式、适配器模式、观察者模式。
4.4 LSP(里式替换原则)
程序中的父类型都应该可以正确地被子类型替换。
4.5 ISP(接口隔离原则)
4.6 DIP(依赖倒置原则)
模块之间交互应该依赖抽象,而非实现。在COLA架构中,领域层不应该直接依赖基础设施层,它们之间的解耦就是通过DIP完成的。
4.9 Rule of Three
第一次,写一个特定方法;第二次,复制上一次的代码;第三次,抽象。
5、设计模式
(本章没有什么具体的内容)
6、模型
6.3 类图
类图不仅可以表示类之间的关系,其表示法还可以表达领域概念之间的关系,非常适合进行领域建模。
6.3.1 类的UML表示法
包括:类名、类的属性、类的操作
可见性:+(public)、-(private)、#(protected)
例:
6.3.2 类的关联关系
表示方法 | 多重性说明 |
---|---|
1——1..1 | 1对1的关系 |
1——0..* | 1对0或多的关系 |
1——1..* | 1对1或多的关系 |
1——0..1 | 1对0或1的关系 |
1——m..n | 1对m到n的关系 |
tips:
1)空心菱形代表聚合关系;
2)实心菱形代表组合关系,同生共死的关系,代码上的体现就是没有setter方法;
6.3.3 类的依赖关系
依赖关系用带箭头的虚线表示,由依赖一方指向被依赖的一方。
实施:作为方法中的参数、作为局部变量、调用另一个类中的静态方法。
6.3.4 类的泛化
泛化关系用带空心三角形的直线来表示。Java中即extends
6.3.5 接口与实现关系
接口与实现关系用带空心三角形虚线来表示。
7、DDD的精髓
7.5 DDD的核心概念
7.5.1 领域实体
判断领域实体一个简单的方法就是“找名词”。
7.5.2 聚合根
聚合根会把一组有相同生命周期、在业务上不可分割的实体和值对象放在一起,只有根实体可以对外暴露引用,这也是内聚性的一种表现。
7.5.3 领域服务
例:MoneyTransferDomainService
1)服务执行的操作代表了一个领域概念,这个概念无法自然地隶属于一个实体或者值对象;
2)被执行的操作涉及领域中的其他对象;
3)操作是无状态的。
7.5.4 领域事件
1)事件命名:推荐DomainName+动词的过去式+Event
2)事件内容:自恰,就是在事件payload中尽量多放数据,这样consumer不需要回查就能处理消息;回查,只在payload中放置id属性。
7.5.5 边界上下文
通过ACL(防腐层)实现
7.6 领域建模方法
7.6.1 用例分析法
1)获取用例描述;2)寻找概念类;3)添加关联;4)添加属性(区分概念和属性);5)模型精化(UML)。
7.6.2 四色建模法
7.8 为什么DDD饱受争议
7.8.1 照搬概念
7.8.2 抽象的灵活性
7.8.3 领域的边界
核心业务逻辑和技术细节相分离
消除Domain对Infrastructure的依赖性
1)使用依赖倒置
2)将Repository上移到Application层,也就是把组装Entity的责任转移给Application。
tip:COLA 2.0采用 2),但是我觉得 1)更合理。