错误处理
使用异常而非返回码
多个条件分支记录错误信息,可以封装进一个方法,在记录异常信息的地方抛出异常,并给出相应信息。在该方法外部捕获,记录异常信息。异常处理和正常业务流程隔离。
缩小异常类型,反例:全部使用Excception。
使用未检异常
代价:违反开闭原则。该方法调用链路上的方法签名都得修改。
给出异常发生的环境说明
应创建信息充分的错误信息,并和异常一起传递出去。
依调用者需要定义异常类
当一个被调用的方法会抛出多种异常时,可以封装下,只抛出一个自定义异常,之前的异常信息传递到自定义异常里。
定义常规流程
try{
MealExpense e=expenseReportDAO.getMeals(x);
total+=e.getTotal;
}catch(MealExpenseNotFound e){
total+=getMealPerDiem());
}
修改为
- 定义一个特例类:
PerDiemMealExpenses
,继承于MealExpense
,并重写getTotal
方法。 在GetMeals
方法里不抛异常,而是返回特例类。这样就不用catch了。
当存在特殊情况时,由被调用方处理特殊情况,调用方只需使用常规流程即可。
特例模式:创建一个类或者配置一个对象,用来处理特例。
别返回null值
多处调用者需要做判空处理,这些是可以封装到被调用的方法里。例如:没有查询到数据时,就返回空列表。
别传递null值
如果禁止参数为null,如果为null,就抛出异常。此时可以使用更优雅的断言。
小结
整洁代码时可读的,但也是强固的。可读与强固并不冲突。
例如返回空列表。
边界
使用第三方代码
第三方代码追求普适性,而使用者可能会有不同的定制化要求,这是矛盾的。为防止第三方提供的数据被随意修改,可将数据封装起来,提供操作数据的方法。例如以前提供一个Map,数据可能会被修改。现在将Map封装到类里,并提供可控的,有限的操作数据的方法,保证数据的安全。
另外还可提升扩展性,当存储数据的数据结构发生变动,不再是Map,此时只需修改操作数据的方法即可,不影响调用者。
不建议使用Map传参,这样传参,参数包含的数据是不可预知的。必要时可以封装下。
浏览和学习边界
通过编写测试来浏览和理解第三方代码。——学习性测试
学习性测试的好处不只是免费
编写学习性测试很容易了解第三方代码,并且当第三方代码更新时,我们可以运行学习性测试,看看程序的行为是否有变化。
使用尚不存在的代码
在开发过程中,需要与第三方交互,但是还没有定义相关接口。这种情况可以自己拟定接口,从而不影响自己的开发进度。在真正的接口定义好时,通过编写适配器,在我们自己拟定的接口和真正的接口中间转换下。
整洁的边界
如果有良好的软件设计,则无需巨大投入和重写即可进行修改。在使用我们控制不了的代码时,必须加倍小心保护资产,确保未来的修改不至于代价太大。
依靠你能控制的东西,好过依靠你控制不了的东西,免得日后受他控制。
单元测试
TDD三定律
第一定律:在编写不能通过的单元测试前,不可变械生产代码。
第二定律:只可编写刚好不可通过的单元测试,不能编译也算不通过。
第三定律:只可编写刚好足以通过当前失败测试的生产代码。
保持测试的整洁
测试代码和生产代码一样重要。
单元测试是敢于重构代码的底气。
整洁的测试
在单元测试中,可读性甚至比在生产代码中还重要。
测试方法中不要违背单一职责原则。构建数据,执行测试目标方法,校验结果,这些该抽取就抽取,要让测试代码易读。
权衡优雅和性能。
每个测试一个断言
这样会造成重复代码,可通过模板模式来解决,共性放在基类,特性放到派生类。还可以单独一个测试类,共性放到@Before
里。
视情况而定,没有绝对的规定,需要权衡,把握那个度。
单元测试中的断言数量应该最小化。
每个测试一个概念,符合单一职责原则,使其在一个抽象层面。
FIRST
- Fast:快速,测试应该快速运行。
- Independent:独立,测试应该相互独立,不因有依赖关系。
- Repeatable:可重复。
- Self-Validating:自足验证,测试可以自己检查是否通过,不应依靠他人。例如输出结果,人为检查是否正确。
- Timely:及时,单元测试应在生产代码前编写。