代码精进之路
语雀完整版:
https://www.yuque.com/g/mingrun/embiys/aiomx9/collaborator/join?token=HXBWiHYsXWN9if5I&source=doc_collaborator# 《代码精进之路》
代码精进之路
前言
- 主要内容
- 技艺部分
- 编程技巧
- 方法论
- 优化代码质量
- 思想部分
- 抽象能力
- 分治思想
- 基本素养
- 实战部分
- 常见应用架构
- COLA
- COLA:
整洁面向对象分层架构(Clean Object-oriented and Layered Architecture)
技艺部分
第1章:命名
好的命名可以极大地提升代码可读性和可理解性,本章主要介绍命名的重要性、命名要注意什么,以及我们如何对不同的软件构建( Artifact )进行命名。
- 把要义浓缩在一两个词中,好的命名能让人不看代码就知道具体含义
- 谚语:在计算机科学中有两件难事:缓存失效和命名。(There are only two hard things in Computer Science: cache invalidation and naming things.)
- 类、方法、变量的命名:要能体现出是什么、干什么,同时类要考虑职责单一性
- 每个概念一个词,要约定好
- 对仗词
- add/remove
- increment/decrement
- open/close
- begin/end
- insert/delete
- show/hide
- create/destroy
- lock/unlock
- source/target
- first/last
- min/max
- start/stop
- get/set
- next/previous
- up/down
- old/new
- 后者限定词
- Total、Sum、Average、Max、Min、Count
- 统一技术语言
- 使用中间变量:将一行代码中的多个表达式拆成多行,提高可读性
- 注释
- 不要复述功能,可以去解释背后意图
第2章:规范
在Google的代码审查( Code Review )实践中,代码是否符合规范( Norms )是最重要的检查项。在本章中,我们将了解必需的规范、如何制定规范,以及如何贯彻实施规范。
- 认知成本
- 人们获取知识所需的代价
- 知识是从无序混乱中中进行归纳总结后发现的规律
- 在软件开发领域混乱是会有很大代价的
- 代码格式
- 缩进、对其、注释格式
- 空行规范
- 日志规范
- error:需要被关注和立刻被解决
- warn:可预知,例如参数校检不通过,虽然不影响业务,但如果输出过多,达到了一个阈值,就有可能是系统设计的问题
- info:记录系统基本运行过程
- debug:输出调试信息
- 异常规范
- 规范一:设置两种异常,BizException和SysException,也就是业务异常和系统异常
- 上面两个异常都使用 unCheackedException,不能使用cheacked异常,会违反开闭原则(不太懂)
- 错误码规范
- 编号错误码:在指定的数字范围内代表一层意思
- 显示错误码
- 显示错误码
- 埋点规范
- 需求
- 业务数据化、数据业务化(业务要沉淀数据,数据要反哺业务)
- 对于产品经理来说,要清楚用户的第一件事情是做什么、接着还会做什么、用户的轨迹和动线是怎样的。
- 对于运营人员来说,要清楚一次活动带来了多少访问流量、转化率如何、通过不同渠道来的用户表现怎么样、最终这些用户有多少转化成了活跃用户。
- 上面的需求都可以使用埋点实现,大致阶段如下:
- 上面的需求都可以使用埋点实现,大致阶段如下:
- 规范
- 为什么:埋点规范是为了确保被采集上来的数据能够被统计分析
- 阿里巴巴超级位置模型(Super Position Model,SPM)
- 这个案例如有需要可以重点看一下
- 架构规范
- 分层、分包(定制Maven Archetype ?)
- 防止破窗(效应)
第3章函数。
有时即使你不采用任何面向对象( Object Oriented,00 )技术,只把函数写好,代码也会呈现完全不一样的风貌。本章介绍许多写函数的技巧和方法,非常实用。
- 函数是什么
- 一个量到另一个量的过程
- 软件中则是代码集合,是一个功能模块
- 封装判断
- 将判断中的大段判断代码封装成一个函数,提高可读性
- 函数参数
- 参数越少越好,如果大于三个函数,就应该考虑封装成类了(对一组概念进行抽象)
- 短小的函数
- 定下硬性指标,20行代码一个函数
- 职责单一
- 职责单一,提高内聚,长方法按照功能进行拆分
- 精简辅助代码
- 辅助代码:各种校检、日志,降低了可读性,所以应该拆分函数,让辅助代码与业务主流程代码分开
- 优化判空
- 使用optional,进行优化多层判空嵌套
- 优化缓存判断
- 使用框架来避免缓存判空代码冗余
- 优雅降级
- 例如hytrix中的降级方式
- 组合函数模式
- 是一个对于提高代码可读性立竿见影的 编程原则
- 要求:在入口函数中,只包含一系列执行的步骤,具体的实现交给各个私有方法去实现,这里可以联想到AbstractApplicationContext中的refresh()包含的12个步骤
- 抽象层次一致性(Single Level of Abstration Principle,SLAP)
- 要求函数体上的内容必须要在同一个抽象层次上
- 在一个函数中塞入过多逻辑时,要注意他们是不是在一个抽象层次上,否则可能会导致一定程度的混乱
- 金字塔结构, SLAP实际上遵循了金字塔结构,同样refresh()也是符合的,他是一个很好的示范
- 函数式编程
- 函数式编程的重点:你可以把函数(你的代码)作为参数传递给另一个函数,原先只可以传递一个值
- 作用&优势:
- 减少代码冗余、更简介
- 无共享变量操作,线程安全
- 四种简介度由低到高的写法
//经典类
Function<String, Integer> strToIntClass = new StrToIntClass();
public static class StrToIntClass implements Function<String, Integer>{@Overridepublic Integer apply(String s) {return Integer.parseInt(s);}
}//匿名类
Function<String, Integer> strToIntAnanymousClass = new Function <String, Integer>(){@Overridepublic Integer apply(String s) {return Integer.parseInt(s);}
};//Lamda实现
Function<String, Integer> strToIntLammda =s-> Integer.parseInt(s);//方法引用
Function<String,Integer>strToIntMethodRefrence=Integer::parseInt;
第4章设计原则。
本章介绍了很多前人总结的优秀设计原则,包括最著名的SOLID,它为我们提供了非常好的0O设计指导原则,比如扩展性的终极目标是满足OCP。我个人特别推崇DIP,因为它是架构设计的重要指导原则。
SOLID,其中开闭原则和李氏转换原则是设计目标,另外三个是设计方法
- Single Responsibility Principle(SRP):单一职责原则。
- Open Close Principle(OCP):开闭原则。
- 对扩展开放,对修改关闭
- 用途:新增代码不会影响原来代码,就不会引发漏洞
- 如何实现:继承和多态(继承、重写,父类应用指向子类对象)
- 应用
- 装饰者模式:不改变被装饰对象的前提下,包装一个新类来拓展功能
- 策略模式:通过制定一个策略接口,让不同的策略实现成为可能;
- 适配器模式,在不改变原有类的基础上,让其适配(Adapt)新的功能
- 观察者模式,可以灵活地添加或删除观察者(Listener)来扩展系统的功能。
- Liskov Substitution Principle(LSP):里氏替换原则。
- 概念:子类可以替换任何基类出现的地方
- 对LSP的破坏
- instanceof:通过强转才能使用子类函数和 用instanceof判断子类型的地方,这种就是父类无法使用子类定义的方法,解决这种问题可以让子类函数用一种更抽象通用的方式定义在父类中
- 子类覆盖父类函数:正方形-矩形问题(子类继承了父类方法,并且改变了其含义,这样做里氏替换时就会出现问题)
- Interface Segregation Principle(ISP):接口隔离原则。
- 接口隔离原则认为不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口要好。
- Dependency Inversion Principle(DIP):依赖倒置原则。
- DIP要求高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖抽象。
- 还有其它几个设计思想在笔记上
第5章设计模式。
好的设计模式能够使代码具有恰到好处的灵活性和优雅性,工程师之间的沟通也会变得简单。本章没有详细介绍GoF中的全部24种模式,只重点介绍几个日常使用频率高、实用性强的设计模式。
- 模式
- 典型的问题解决方案
- GOF
- 拦截器模式
- 它相较于代理模式,更突出前置处理和后置处理
- 它相较于代理模式,更突出前置处理和后置处理
- 怎么实现
- 不同于代理模式用的反射,它的实现依赖于面向对象
- 插件模式
- 他与普通的扩展模式相比(例如策略模式),区别在于插件是从软件的外部进行扩展,不用再打包部署,一般他们都能做到热部署(即插即用)
- 管道模式
- 涉及思想:分治
- 链式管道
- 角色
- (1)阀门:处理数据的节点。
- (2)管道:组织各个阀门。
- (3)客户端:构造管道并调用。
- 流处理
- 应用
- UNIX中的管道命令
|
,它将一个程序的输出转到另一个程序中,如cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
- UNIX中的管道命令
- Java8中的stream Api,一定要好好研究下其底层
第6章模型。
软件工程就是一个对现实世界的问题进行分析、抽象、建模,然后转换成计算机可以理解的语言,解释执行,实现特定
业务逻辑的过程。本章主要介绍了什么是模型、软件工程中常见的建模方法论,以及如何运用这些模型为软件服务。
- 什么是模型
- 模型是对现实世界的简化抽象。建立模型有很多方法,并不意味着要用特定的符号、工具和流程。我们只是想在研究复杂东西时,让其中的一些部分易于理解。因此,无论使用何种建模工具和表示法(Notation),只要有助于我们对问题域的理解,均可认为是好的模型。
- 物理模型:一般是指对物理形态的等比例缩小
- 数学模型:可指一组方程,定量或定性的描述系统各个变量之间 之间的因果关系或相互关系
- 概念模型:对真实世界中问题域内的事物的描述,是领域实体,而不是对软件设计的描述,它和技术无关。
概念模型将现实世界抽象为信息世界,把现实世界中的客观对象抽象为某一种信息结构,这种信息结构并不依赖计算机。
- 概念模型:对真实世界中问题域内的事物的描述,是领域实体,而不是对软件设计的描述,它和技术无关。
- 思维模型:解决一些特定问题的思维套路,例如金字塔模型
- 模型不能代替现实:带着审视的眼光去看待模型,就像牛顿和爱因斯坦对重力的解释一样
- 统一建模语言(Unified Modeling Language,UML)
- 分为结构型和行为型
- 分为结构型和行为型
- 类图
- 表示法
- 类与类之间的关联关系
- 双向关联
- 限定关联(在某些场景下的限制)
- 单向关联
- 自关联,如
public class Node {private Node subNode;
}
- 聚合关系:表示整体与部分的联系,例如Car和Engine, Engine是可以独立存在的
- 组合关系:他与聚合的区别是,组合是“同生共死”的,整体对象不存在,成员也会不存在,例如Head和Mouth,在代码中通常在整体类的构造方法中实例化成员类
- 类与类之间的依赖关系:是一种使用关系,特定事物的改变可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时,使用依赖关系。大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。
- 类的泛化关系:继承,例如Person 和它的子类Teacher和Student
- 接口实现关系
- 领域建模
- 概述:从本质上来说,软件开发过程就是问题空间到解决方案空间的一个映射转化
- 概述:从本质上来说,软件开发过程就是问题空间到解决方案空间的一个映射转化
- 问题空间就是系统要解决的领域问题,因此,也可以简单理解为一个领域就对应一个问题空间,是一个特定范围边界内的业务需求的总和。
- 领域模型”就是“解决方案空间”,是针对特定领域里的关键事物及其关系的可视化表现,是为了准确定义需要解决问题而构造的抽象模型,是业务功能场景在软件系统里的映射转化,其目标是为软件系统的构建统一的认知。
- 例如:
- 请假系统解决的是人力工时的问题,属于人力资源领域,对口的是HR部门;
- 费用报销系统解决的是员工和公司之间的财务问题,属于财务领域,对口的是财务部门;
- 电商平台解决的是网上购物问题,属于电商领域。
- 可以看出,每个软件系统本质上都解决了特定的问题,属于某一个特定领域,实现了同样的核心业务功能来解决该领域中核心的业务需求。
- 敏捷建模
- 模型能用来沟通和理解。
- 力争用简单的工具创建简单的模型。
- 我们知道需求是变化的,因此创建模型时要拥抱变化。
- 重点是交付软件,而不是交付模型。模型能带来价值时,我们就使用;如果模型没有价值,不能加速软件的交付,就不创建它们。
- 广义模型
- C4模型:提出使用上下文(Context)、容器(Container)、组件(Component)和代码(Code)等一系列分层的图表,来描述不同缩放级别的软件架构
- UI流程图
- 业务模型:用图形话的方式来描述业务
第7章DDD的精髓。
领域建模是面向对象技术的精髓,本章的主要思想都来自于领域驱动设计( Domain Driven Design
DDD), 但是并没有教条地照搬,而是结合实践对DDD进行了改良、萃取和优化。
- 什么是DDD
- DDD是Eric Evans在2003年出版的《领域驱动设计:软件核心复杂性应对之道》(Domain-Driven Design: Tackling Complexity in the Heart of Software)一书中提出的具有划时代意义的重要概念,是指通过统一语言、业务抽象、领域划分和领域建模等一系列手段来控制软件复杂度的方法论。
- 初步体验DDD
- 案例:银行转账
- 改造前
- 传统的写法,贫血模式,在service中提供方法
public class MoneyTransferServiceTransactionScriptImpl implements MoneyTransferService {private AccountDao accountDao;private BankingTransactionRepository bankingTransactionRepository;// . . .@Overridepublic BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {Account fromAccount = accountDao.findById(fromAccountId);Account toAccount = accountDao.findById(toAccountId);// . . .double newBalance = fromAccount.getBalance() - amount;//根据透支政策判断 是贷款还是直接拒掉switch (fromAccount.getOverdraftPolicy()) {case NEVER:if (newBalance < 0) {throw new DebitException("Insufficient funds");}break;case ALLOWED:if (newBalance < -limit) {throw new DebitException("Overdraft limit (of " + limit + ") exceeded: " + newBalance);}break;}fromAccount.setBalance(newBalance);toAccount.setBalance(toAccount.getBalance() + amount);BankingTransaction moneyTransferTransaction = new MoneyTranferTransaction(fromAccountId, toAccountId, amount);bankingTransactionRepository.addTransaction(moneyTransferTransaction);return moneyTransferTransaction;}
}
- 改造后
- 使用领域建模的方式,Account不再贫血,原本他只包含属性的get和set方法,现在它提供了其它行为和业务逻辑
public class Account {private String id;private double balance;private OverdraftPolicy overdraftPolicy;// 省略。。。public double balance() {return balance; }//借钱public void debit(double amount) {this.overdraftPolicy.preDebit(this, amount);this.balance = this.balance - amount;this.overdraftPolicy.postDebit(this, amount);}//贷款public void credit(double amount) {this.balance = this.balance + amount;}
}
- 原来的透支策略类,也不仅仅是一个Enum了(type)
public interface OverdraftPolicy {void preDebit(Account account, double amount);void postDebit(Account account, double amount);
}public class NoOverdraftAllowed implements OverdraftPolicy {public void preDebit(Account account, double amount) {double newBalance = account.balance() - amount;if (newBalance < 0) {throw new DebitException("Insufficient funds");}}public void postDebit(Account account, double amount) {}
}public class LimitedOverdraft implements OverdraftPolicy {private double limit;//省略 。。。 public void preDebit(Account account, double amount) {double newBalance = account.balance() - amount;if (newBalance < -limit) {throw new DebitException("Overdraft limit (of " + limit + ") exceeded: "+newBalance);}}public void postDebit(Account account, double amount) {}
}
- 而Domain Service只需要调用Domain Entity对象完成业务逻辑。
public class MoneyTransferServiceDomainModelImpl implements MoneyTransferService {private AccountRepository accountRepository;private BankingTransactionRepository bankingTransactionRepository;//省略 。。。@Overridepublic BankingTransaction transfer( String fromAccountId, String toAccountId, double amount) {Account fromAccount = accountRepository.findById(fromAccountId);Account toAccount = accountRepository.findById(toAccountId);// 省略 。。。fromAccount.debit(amount);toAccount.credit(amount);BankingTransaction moneyTransferTransaction =new MoneyTranferTransaction(fromAccountId,toAccountId,amount);bankingTransactionRepository.addTransaction(moneyTransferTransaction);return moneyTransferTransaction;}
}
- 数据驱动和领域驱动
- 数据驱动(传统方式,随着项目推进复杂度会急剧增加)
- 研发流程:需求分析 -> 数据建模(ER图)-> -建库建表,写DAO层> 编写业务逻辑
- 案例(CRM 客户关系管理系统)
- E-R图
- E-R图
- 产出(几张表,和对这些表中数据操作的脚本)
- 领域驱动
- 概念:领域驱动设计关心的是业务中的领域划分(战略设计)和领域建模(战术设计),其开发过程不再以数据模型为起点,而是以领域模型为出发点,研发过程如下图所示。领域模型对应的是业务实体,在程序中主要表现为类、聚合根和值对象,它更加关注业务语义的显性化表达,而不是数据的存储和数据之间的关系。这是“领域驱动设计”和“数据驱动设计”之间显著的区别。
- 概念:领域驱动设计关心的是业务中的领域划分(战略设计)和领域建模(战术设计),其开发过程不再以数据模型为起点,而是以领域模型为出发点,研发过程如下图所示。领域模型对应的是业务实体,在程序中主要表现为类、聚合根和值对象,它更加关注业务语义的显性化表达,而不是数据的存储和数据之间的关系。这是“领域驱动设计”和“数据驱动设计”之间显著的区别。
- 领域模型(其特点是描述更加贴近业务,完整表达了业务语义)
- 领域模型(其特点是描述更加贴近业务,完整表达了业务语义)
- 领域划分(将问题域划分为合适的子域,然后在域中进行建模)
- 领域划分(将问题域划分为合适的子域,然后在域中进行建模)
- ORM
- 从上图中可以看出,领域模型和数据模型并不是一一对应的,但也有趋同的时候 ,大部分情况都需要做一层映射,这种技术有一个名称叫做对象关系映射(Object Relationship Mapping,ORM)
- 从上图中可以看出,领域模型和数据模型并不是一一对应的,但也有趋同的时候 ,大部分情况都需要做一层映射,这种技术有一个名称叫做对象关系映射(Object Relationship Mapping,ORM)
- ORM的问题在于太理想化,希望通过工具把数据建模和领域建模合一,,这样搞注定是失败的,比如上面的案例,公海和私海只在领域模型中存在,所以Hibernate和JPA走向衰落,mybatis则是只专注于数据表和DO之间的简单映射
- DDD的优势
- 统一语言
- 概念:目标是创造可以被业务、技术和代码自身无歧义使用的共同术语,即统一语言,代码、类、方法、属性和模块的命名必须和统一语言相匹配,必要的时候需要对代码进行重构!(产品需求文档、设计文档、代码已经团队的日常交流中建立通用的语言)
- 面向对象
- DDD的核心是领域驱动,通俗的说,他是先找到业务中的领域模型,然后以领域模型为中心,驱动项目开发。领域模型的精髓在于面向对象分析、对事物的抽象能力。
- DDD鼓励我们接触到需求后第一步就是考虑领域模型,而不是将其切割成数据和行为,然后用数据库实现数据,用服务实现行为,最后造成需求的首尾分离。DDD会让你首先考虑业务语言,而不是数据。DDD强调业务抽象和面向对象编程,而不是过程式业务逻辑实现。重点不同,导致编程世界观不同。
- 业务语义显性化
- 分离业务逻辑和技术细节(二者分开处理降低复杂度)
- 分离业务逻辑和技术细节(二者分开处理降低复杂度)
- 数据库:业务逻辑不应该受限于存储方式,也就是不论你是使用关系型数据库还是NoSQL,都不应该影响业务逻辑的实现。数据本身很重要,但数据库技术仅仅是一个实现细节。
- UI:UI只是一种I/O设备的呈现,Web、WAP和Wireless都是不同的I/O,我们的核心业务逻辑应该与如何呈现解耦,以及针对不同的端可以使用不同的适配器(Adaptor)去做适配。
- 框架:不要让框架侵入我们的核心业务代码,以Spring为例,最好不要在业务对象中到处写@autowired注解。业务对象不应该依赖框架。
- DDD的核心概念
- 领域实体
- 领域实体
- 聚合根
- 聚合根(Aggregate Root)是DDD中的一个概念,是一种更大范围的封装,会把一组有相同生命周期、在业务上不可分割的实体和值对象放在一起,只有根实体可以对外暴露引用,这也是一种内聚性的表现。
- 案例(也可以用一颗树来做比喻):账号(Account)是客户信息(CustomerInfo)Entity和值对象(Address)的聚合根,交易(Tansaction)是流水(Journal)的聚合根,流水是因为交易才产生的,具有相同的生命周期。
- 案例(也可以用一颗树来做比喻):账号(Account)是客户信息(CustomerInfo)Entity和值对象(Address)的聚合根,交易(Tansaction)是流水(Journal)的聚合根,流水是因为交易才产生的,具有相同的生命周期。
- 领域服务
- 概念:有些领域中的动作是一些动词,看上去并不属于任何对象。它们代表了领域中的一个重要的行为,所以不能忽略它们或者简单地把它们合并到某个实体或者值对象中。当这样的行为从领域中被识别出来时,推荐的实践方式是将它声明成一个服务。这样的对象不再拥有内置的状态,其作用仅仅是为领域提供相应的功能。Service往往是以一个活动来命名,而不是Entity来命名。
- 案例:
例如在银行转账的例子中,转账(transfer)这个行为是一个非常重要的领域概念,但是它发生在两个账号之间,归属于账号Entity并不合适,因为一个账号Entity没有必要去关联它需要转账的账号Entity。在这种情况下,使用MoneyTransferDomainService就比较合适了。
- 案例:
- 识别领域服务
- 服务执行的操作代表了一个领域概念,这个领域概念无法自然地隶属于一个实体或者值对象。
- 被执行的操作涉及领域中的其他对象。
- 操作是无状态的
- 领域事件
- 概念:领域事件(Domain Event)是在一个特定领域由一个用户动作触发的,是发生在过去的行为产生的事件,而这个事件是系统中的其他部分或者关联系统感兴趣的。
- 事件命名:
事件是表示发生在过去的事情,所以在命名上推荐使用Domain Name +动词的过去式+ Event,这样可以更准确地表达业务语义。例如,在银行转账的例子中,对于转账成功和失败我们都需要发出事件通知,可以定义两个领域事件如下。
(1)MoneyTransferedEvent:表示转账成功发出的事件。
(2)MoneyTransferFailedEvent:表示转账失败发出的事件。
- 事件命名:
- 事件内容在计算机术语中叫作payload,有以下两种形式。
(1)自恰(Enrichment):就是在事件的payload中尽量多放数据,这样consumer不需要回查就能处理消息,也就是自恰地处理消息。
(2)回查(Query-Back):这种方式是只在payload放置id属性,然后consumer通过回调的形式获取更多数据。这种形式会加重系统的负载,可能会引起性能问题。
- 事件内容在计算机术语中叫作payload,有以下两种形式。
- 边界上下文
- 概念:领域实体的意义是有上下文的,比如同样是Apple,在水果店和苹果手机专卖店中表达出的含义就完全不一样。边界上下文(Bounded Context)的作用是限定模型的应用范围,在同一个上下文中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。
- 防腐层:那么不同上下文之间的业务实体要如何实现交互呢?就像关系数据库和对象之间需要ORM一样,不同上下文之间的实体也需要映射。在DDD中,这种机制叫作上下文映射(Context Mapping),我们可以使用防腐层(Anti-Corruption)来完成映射的工作。
- 如图所示,在我们开发的CRM系统中,商家的客户大部分是来自于ICBU网站的会员,虽然二者有很多属性都是一样的,但我们还是有必要引入防腐层来做上下文映射,主要有以下两个原因。
(1)虽然属性大部分一样,但二者的作用和行为在各自上下文中是不一样的。
(2)解耦影响,加入了防腐层之后,网站的会员变化就不会影响到CRM系统了。
- 如图所示,在我们开发的CRM系统中,商家的客户大部分是来自于ICBU网站的会员,虽然二者有很多属性都是一样的,但我们还是有必要引入防腐层来做上下文映射,主要有以下两个原因。
- 领域建模方法
- 用例分析法
- 获取用例描述:使用用例 将问题域的知识表现出来
- 寻找概念类:对获取的用例描述进行语言分析,识别名词和名词短语
- 添加关联:代表着两个模型之间存在语义联系,在用例中的表现为两个名词被动词连接起来
- 添加属性
- 模型精化
- 四色建模法
- 把模型分为四种
- 把模型分为四种
- 业务关键时刻
- 这种对象表示那些在某个时间点存在或者会存在一段时间。这样的对象往往表示了一次外界的请求,比如一次询价(Quotation)、一次下单(Order)或者一次租赁(Rental)。
- 角色
- 表示一种角色,通常由人或物来承担,会有相应的责任和权力。一般,一个Moment-interval对象会关联多个Role。例如,一次下单涉及两个Role,分别是客户(Customer)和商品(Product)。
- 人-事-物
- 这种对象往往表示一种客观存在的事物,例如人、组织、产品或者配件等,这些事物会在一种moment-interval 中扮演某个Role。例如,某个人既会在一次购买中扮演Customer的角色,也可以在询价中扮演询价人的角色。这类对象的重要程度排在第三,一般用绿色来表示。
- 描述
- 这种对象一般是用于分类或者描述性的对象,它的属性一般是这一类事物都有的属性,一般用蓝色来表示。
- 案例:值得一看
- 模型演化
- 为什么DDD饱受争议
- 照搬概念,没有达到降低系统复杂度的效果
- 抽象的灵活性,每个人的理解不同,最终可能导致建模出现分歧
- 领域层的边界
图
- 在线电子书店的关键业务流程
- 在线电子书店的业务关键时刻对象
- 在线电子书店的人-事-物对象
- 在线电子书店的角色对象
- 在线电子书店的描述对象
第8章 抽象
- 伟大的抽象
- 到底什么是抽象
- 抽象和具象是相对应的概念,“抽"就是抽离,“象"就是具象。从字面上理解抽象,就是从具体中抽离出来。英文的抽象abstract来自拉丁文abstractio,它的原意是排除、抽出。
- 按照维基百科上的解释,抽象是指为了某种目的,对一个概念或一种现象包含的信息进行过滤,移除不相关的信息,只保留与某种最终目的相关的信息。例如,一个“皮质的足球”,我们可以过滤它的质料等信息,得到更一般性的概念,也就是“球”。从另一个角度看,抽象就是简化事物,抓住事物本质的过程。
- 抽象是oo的基础
- 面向对象(Object Oriented,OO)的思考方式,就是万物皆对象。抽象帮助我们将现实世界的对象抽象成类,完成从现实世界的概念到计算机世界的模型的映射。例如,有一堆苹果,如果对其进行抽象,我们可以得到Appl这个类,通过这个类,我们可以实例化一个红色的苹果:new Apple("red")。此时,如果我们需要把香蕉、橘子等水果也纳入考虑范围,那么Apple的抽象层次就不够了,我们需要Fruiti这个更高层次的抽象来表达“水果"的概念。
- 面向对象的思想主要包括3个方面:面向对象的分析(Object Oriented Analysis,OOA)、面向对象的设计(Object Oriented Design,OOD),以及我们经常提到的面向对象的编程(Object Oriented Programming,O0P)。
- OOA是根据抽象关键问题域来分解系统。
OOD是一种提供符号设计系统的面向对象的实现过程,它用非常接近实际领域术语的方法把系统构造成“现实世界"的抽象。
OOP可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反,传统的程序设计主张将程序看作一系列函数的集合,或者更直接些,就是一系列对计算机下达的指令。
- 抽象的层次性
对同一个对象的抽象是有不同层次的。层次越往上,抽象程度越高,它所包含的东西就越多,其含义越宽泛,忽略的细节也越多;层次越往下,抽象程度越低,它所包含的东西越少,细节越多。也就是我们常说的,内涵越小,外延越大;内涵越大,外延越小。不同层次的抽象有不同的用途。 - 如何进行抽象
- 寻找共性:消除重复,合并同类项
- 提升抽象层次:例如把apple和watermelon抽象为fruit
- instanceof的应用:例如使用instacneof判断一个类是否是 apple类,是的话判断他是否可口,这是可以把是否可口的方法 放到父类fruit中
- 构筑金字塔:抽象层次
- 提升抽象思维:多阅读,多总结
- 领域建模训练
第9章:分治
- 分治:归并,二分,k的选择
- 函数分解
- 写代码的两次创造
- 第一遍实现功能,不求完美
- 第二遍重构优化,思考命名合理吗?职责单一吗?满足OCP吗?函数是否过长?抽象是否合理?等问题
- 分治模式:责任链、管道、装饰模式,其中都有分治的思想。就责任链模式来说,我们不会把处理一件事情需要的所有职责都放在一个组件中,而是放在多个组件中完成,形成一个链条。这样不仅增加了可扩展性,也使每个组件的职责变得更加单一,更容易维护。
- 分层设计
- 分层网络模型
- 分层架构:分层架构的目的是通过分离关注点来降低系统的复杂度,同时满足单一职责、高内聚、低耦合、提高可复用性和降低维护成本,也是一种典型的分治思想。
严格的分层,只能调用下一层的服务,另一种分层则是可以调用下面所有的分层
- 横切和竖切:例如分库分表
第10章:技术人的素养
- 不教条:就像Effective Java一书的作者Joshua Bloch说的,“同大多数学科一样,学习编程艺术首先要学会基本的规则,然后才能知道什么时候去打破规则”。
- 选择软件开发过程绝不是要么瀑布(Vaterfall)),要么敏捷(Agile)这么简单。实际上,软件开发的生命周期风格类似一个连续光谱一一有从瀑布式到敏捷,以及它们之间的多种可能性。
- 瀑布与敏捷的区别
- 贫血还是敏捷:
- 选择软件开发过程绝不是要么瀑布(Vaterfall)),要么敏捷(Agile)这么简单。实际上,软件开发的生命周期风格类似一个连续光谱一一有从瀑布式到敏捷,以及它们之间的多种可能性。
- 一提到事务脚本(Transaction Script)和DDD,人们就习惯性地给它们扣上贫血和充血的帽子。
简单来说,贫血模式提倡模型对象只包含数据,并提供简单的Getter和Setter;而充血模式提倡数据和行为放在一起,是一种更加面向对象的做法。两种模式都有自己的道理,也都有人支持。 - 不能过于纠结是贫血还是充血,核心是控制复杂度,不能过于教条
- 一提到事务脚本(Transaction Script)和DDD,人们就习惯性地给它们扣上贫血和充血的帽子。
- 单体还是分布式:看业务的发展阶段
- 批判性思维
- 成长型思维
- 结构化思维
- 逻辑
- 演绎顺序
- 时间
- 空间
- 程度
- 套路:方法论,路径&经验
- 落地新团队
- 工具化思维
- 好奇心
- 记笔记
- 有目标
- 选择的自由
- 平和心态
- 精进
第11章技术Leaderf的修养
- 技术氛围
- 代码好坏味道:每周轮值分享三个好味道和坏味道
- 技术分享
- CR周报
- 读书会
- 目标管理
- 什么是0KR
- SMART原则
- 0KR设定
- 技术规划
- 当前问题
- 技术领域
- 业务领域
- 团队特色
- 推理阶梯
- Leader和Manager的区别
- 视人为人
实践部分
第12章C0LA架构
- 软件架构
- 狭义定义:软件架构是一个系统的草图,软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通信。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。
- 广义定义:随着互联网发展 软件架构的含义变的更加宽泛,所以有以下层次划分
- 由业务架构师负责,也可以称为业务领域专家、行业专家,属于顶层设计,会影响组织结构和技术架构
- 应用架构:由应用架构师负责,他需要根据业务场景的需要,设计应用的拓扑结构,制定应用规范、定义接口和数据交互协议等。并尽量将应用的复杂度控制在一个可以接受的水平,从而在快速地支撑业务发展的同时,确保系统的可用性和可维护性。COLA架构是一个典型的应用架构,致力于应用复杂度的治理。
- 系统架构:根据业务情况综合考虑系统的非功能属性要求(包括性能、安全性、可用性、稳定性等),然后做出技术选型。对于流行的分布式架构系统,需要解决服务器负载、分布式服务的注册和发现、消息系统、缓存系统、分布式数据库等问题,同时解决如何在CAP(Consistency,Availability,Partition Tolerance)定理之间进行权衡的问题。
- 数据架构:对于规模大一些的公司,数据治理是一个很重要的课题。如何对数据收集、处理,提供统一的服务和标准,是数据架构需要关注的问题。其目的就是统一数据定义规范,标准化数据表达,形成有效易维护的数据资产,搭建统一的大数据处理平台,形成数据使用闭环。
- 物理架构:物理架构关注软件元件是如何放到硬件上的,包括机房搭建、网络拓扑结构、网络分流器、代理服务器、Wb服务器、应用服务器、报表服务器、整合服务器、存储服务器和主机等。
- 运维架构:负责运维系统的规划、选型、部署上线,建立规范化的运维体系。要借助技术手段控制和优化成本,通过工具化及流程提升运维效率,注重运营效益。制定和优化运维解决方案,包括但不限于柔性容灾、智能调度、弹性扩容与防攻击、推动及开发高效的自动化运维和管理工具、提高运维的自动化程度和效率。
- 典型的应用架构
- 分层架构:例如mvc架构,走到极端就是千层面架构,缺点是过于复杂与冗余,会降低系统性能
- CQRS:
- 命令查询分离(Command Query Separation,CQS):命令查询分离(Command Query Separation,CQS),命令是不返回任何结果 void,只改变对象的状态,查询返回结果,不改变对象的状态
- 命令查询职责分离模式(Command Query Responsibility Segregation,CQRS)是对CQS模式的进一步改进而形成的一种简单的架构
- 六边形架构(又称端口-适配器架构):也是一种分层架构,解决传统分层所带来的问题,区别是它是内外架构
- 洋葱架构:洋葱架构在六边形架构的基础上加入了内部层次,与端口适配架构一样,洋葱架构也是通过适配器将应用内核从对基础设施的关注分离开来,洋葱架构如下所示 相较端口架构多了几个领域,并且符合依赖倒转原则,外层依赖内层,而内层不依赖外层
- DDD:准确地说,DDD不是架构,而是一种开发思想。之所以将DDD归类为典型架构,是因为它是我们很多架构的思想来源。比如洋葱架构,其内层(核心业务逻辑)就应该是领域层。当然,COLA也传承了DDD的思想。
另外,DDD带来的最大改变是让我们得以从“数据驱动"转向“领域驱动”,让我们知道领域是应用的核心,其他都是技术细节,随时可以被替换。
- 六边形架构(又称端口-适配器架构):也是一种分层架构,解决传统分层所带来的问题,区别是它是内外架构
- C0LA架构设计
- 分层设计
12.3.2扩展设计
12.3.3规范设计
12.3.4 COLA Archetype
12.4C0LA测试
12.4.1单元测试
12.4.2集成测试
12.4.3 ColaMock
12.5C0LA架构总览
12.6本章小结
第13章工匠平台
13.1项目背景
13.2整理需求
13.3工匠Demo
13.4使用C0LA
13.4.1安装C0LA
13.4.2搭建应用
13.5领域模型
13.5.1领域建模
13.5.2领域词汇表
13.6核心业务逻辑
13.7实现技术细节
13.7.1数据存储
13.7.2控制器
13.8测试
13.8.1单元测试
13.8.2集成测试
13.8.3回归测试
13.9本章小结