防御式编程有这样一种思维,类似于驾驶汽车,永远不能确定其他司机会做什么,同时要保护好自己,哪怕是其他司机犯的错误。
也就是说,子程序不因传入错误数据而被破坏,哪怕是其他子程序产生的错误数据。
因此,如何处理错误数据?
有三种方式:
- 检查来源于外部数据的值
- 检查子程序所有输入参数的值
- 决定如何处理错误的输入数据
一、断言
断言是用在开发和维护阶段的,生产环境又被去除,以免降低系统的性能。
错误处理代码
- 处理预期会发生的情况
- 检查有害的输入数据
- 对错误进行正确反映
断言
- 处理绝对不应该发生的情况。
- 检查代码中的bug
- 需要重新修改代码,重新编译
- 类似于可执行的注释
在IDEA中开启断言,菜单栏Run -> Edit Configurations -> VM Options 中输入
-ea
,就可以开启了。
示例:
private int volume(int length, int width, int height){
//前置条件
assert length>0 && length<=100 :"长度必须大于0且小于等于100";
assert width>0 && width<=100:"宽度必须大于0且小于等于100";
assert height>0 && height<=100:"高度必须大于0且小于等于100";
int volume = length*width*height;
//后置条件
assert volume>0 && volume<=1000000:"体积必须大于0且小于等于1000000";
return volume;
}
如果变量length
, width
, height
来源于外部系统,应该用错误处理代码。
而如果来源于可信的系统内部,使用断言非常合适。
虽然建议要么只使用断言,要么只使用错误处理代码。
然而,对于特别复杂的系统,有时错误处理代码和断言一起使用。
private int volume(int length, int width, int height){
//断言
assert length>0 && length<=100 :"长度必须大于0且小于等于100";
assert width>0 && width<=100:"宽度必须大于0且小于等于100";
assert height>0 && height<=100:"高度必须大于0且小于等于100";
//错误处理代码
if (length <= 0){
length = 0;
}else if(length > 100){
length = 100;
}
if (width <= 0){
width = 0;
}else if(width > 100){
width = 100;
}
if (height <= 0){
height = 0;
}else if(height > 100){
height = 100;
}
int volume = length*width*height;
//断言
assert volume>0 && volume<=1000000:"体积必须大于0且小于等于1000000";
return volume;
}
二、错误处理技术
- 返回中立值 就是继续执行,并返回没有危害的数据
- 返回下一个正确数据
- 返回前一个相同数据
- 返回最接近的合法值
- 记录日志并继续执行
- 返回错误码
- 全局处理错误
- 显示出错信息
- 局部处理错误
10.关闭程序
三、异常
异常是把代码中的错误或异常事件传递给调用方代码的一种特殊手段。
通常在子程序不知道如何处理错误时,抛出异常。
如何使用异常?
1.只在真正的例外情况下,才使用异常,不要推卸责任。
错误情况可以局部处理,就去处理,不要抛异常。
比如空指针异常NullPointerException
,数组越界异常IndexOutOfBoundsException
,可以提前检查。检查不到才抛异常。
2.抛异常要在同一抽象层次,并且异常要做好分类。
也就是说,不要在业务层次抛底层的异常,也不要try一大段代码,区分稳定代码和非稳定代码。
3.被捕获后不要用来做流程控制和条件控制,或者不处理。
做流程控制效率太慢,不处理的话,就抛给调用方。
4.异常消息应包含所有导致异常发生的所有信息。
比如数组越界异常,应该包含上界,下界,非法下标值等。
5.推荐使用全局异常处理机制。
四、隔离程序
可以创建验证类拦截外部不可信的数据,经过验证的数据到达内部,默认是可信的。
也可以在,类的层次进行验证,类的公用方法可以假设数据是不安全的,负责检查数据并进行清理。而私有方法假定数据是安全的。