由外而内:发现和形成设计职责
设计是一种信息不完全情况下的决策:从最开始我们并不知道要用什么数据结构可能是最好的,如果一开始就从底层数据结构开始写,需要耗费更多脑力。
我们首先应该写确定的功能性代码,一般来说外层功能相对确定,但是底层数据结构我们不一定知道。
延迟决策到最后时刻关键信息经常会自然显现
由外而内的变成允许暂时忽略不重要的细节,“意图导向编程 Programming By Intention”
收益:
- 职责分配实在编码过程中即使完成的,设计和编码真正成为一件事情
- 代码的可理解性可以得到保证(声明式编程),消除了大多数注释的必要性。
- 以终为始,渐近导出设计契约
- 延迟复杂的细节性决策,聚焦于当前阶段的关键行为
- 利用了IDE的“自动纠错”能力,提升了编码速度
由外而内编程的两个要素:
- 保持同一个层次的抽象
- 合理分配职责
小结:
- 从应用层开始写,可以用应用场景驱动,促进领域对象和领域服务的职责持续生长
- 意图导向,描述期望的程序行为,同时完成职责分配
- 逐层深入,重复上述步骤
- 设计领域层对象时,要结合领域对象的职责考虑,并合理区分哪些属于领域层,哪些不属于领域层
测试先行:表达和实现设计契约
测试先行 = 意图导向编程 + 用测试表达设计契约
后置自动化单元测试的问题:
- 上下文丢失:难以回忆设计场景,出现设计场景丢失
- 可测性缺乏:不知道老代码的功能职责,依赖难以断开
- 情绪和动机:仅仅在补覆盖率的时候想做单元测试,但功能效果不好
推荐实践:
- 自动化测试代码先于产品代码完成
- 利用测试完善接口的定义,完善契约,可以用测试来做接口说明
- 增加REST接口,并在集成环境中测试
- 覆盖率是一种指示器,也是测试先行的自然结果
单元测试不是“白盒”测试,测试用例的本质是在描述接口职责
收益:
- 聚焦于外部行为而不是具体实现
- 自动化测试形成了代码的功能手册,可以作为活文档
- 可测性得到了保证,不会出现依赖解不开,职责分不清的问题
- 好的可测性带来了模块化
- 测试线性容易推动进展
- 持续建立信心
持续演进:契约是设计质量的保证
legacy code == code without tests
在有测试的前提下,代码可以重构、演进