重构函数调用-Replace Exception with Test以测试取代异常十五
1.以测试取代异常
1.1.使用场景
面对一个调用者可以预先检查的条件,你抛出了一个异常。修改调用者,使它在调用函数之前先做检查
异常的出现是程序语言的一大进步。运用Replace Error Code with Exception (310),异常便可协助我们避免很多复杂的错误处理逻辑。但是,就像许多好东西一样,异常也会被滥用,从而变得不再让人愉快(就连味道极好的Aventinus啤酒,喝得太多也会让我厌烦[Jackson])。“异常”只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误的行为,而不应该成为条件检查的替代品。如果你可以合理期望调用者在调用函数之前先检查某个条件,那么就应该提供一个测试,而调用者应该使用它。
1.2.如何做
- 在函数调用点之前,放置一个测试语句,将函数内catch区段中的代码复制到测试句的适当if分支中。
- 在catch区段起始处加入一个断言,确保catch区段绝对不会被执行。
- 编译,测试。
- 移除所有catch区段,然后将try区段内的代码复制到try之外,然后移除try区段。
- 编译,测试。
1.3.示例
下面的例子中,我以一个ResourcePool对象管理一些创建代价高昂而又可以重复使用的资源(例如数据库连接)。这个对象带有两个“池”(pool):一个用以保存可用资源,一个用以保存已分配资源。当用户请求一份资源时,ResourcePool对象从“可用资源池”中取出一份资源交出,并将这份资源转移到“已分配资源池”。当用户释放一份资源时,ResourcePool对象就将该资源从“已分配资源池”放回“可用资源池”。如果“可用资源池”不能满足用户的请求,ResourcePool对象就创建一份新资源。
资源供应函数可能如下所示:
class ResourcePool
Resource getResource() {
Resource result;
try {
result = (Resource) _available.pop();
_allocated.push(result);
return result;
} catch (EmptyStackException e) {
result = new Resource();
_allocated.push(result);
return result;
}
}
Stack _available;
Stack _allocated;
在这里,“可用资源用尽”并不是一种意料外的事件,因此我不该使用异常表示这种情况。
为了去掉这里的异常,我首先必须添加一个适当的提前测试,并在其中处理“可用资源池为空”的情况
Resource getResource() {
Resource result;
if (_available.isEmpty()) {
result = new Resource();
_allocated.push(result);
return result;
}
else {
try {
result = (Resource) _available.pop();
_allocated.push(result);
return result;
} catch (EmptyStackException e) {
result = new Resource();
_allocated.push(result);
return result;
}
}
}
现在getResource()应该绝对不会抛出异常了。我可以添加断言保证这一点
Resource getResource() {
Resource result;
if (_available.isEmpty()) {
result = new Resource();
_allocated.push(result);
return result;
}
else {
try {
result = (Resource) _available.pop();
_allocated.push(result);
return result;
} catch (EmptyStackException e) {
Assert.shouldNeverReachHere("available was empty on pop");
result = new Resource();
_allocated.push(result);
return result;
}
}
}
class Assert...
static void shouldNeverReachHere(String message) {
throw new RuntimeException (message);
}
编译并测试。如果一切运转正常,就可以将try 区段中的代码拷贝到try 区段之外,然后将区段全部移除:
Resource getResource() {
Resource result;
if (_available.isEmpty()) {
result = new Resource();
_allocated.push(result);
return result;
}
else {
result = (Resource) _available.pop();
_allocated.push(result);
return result;
}
}
在这之后我常常发现,可以对条件代码加以整理。本例之中我可以使用Consolidate Duplicate Conditional Fragments (243):
Resource getResource() {
Resource result;
if (_available.isEmpty())
result = new Resource();
else
result = (Resource) _available.pop();
_allocated.push(result);
return result;
}