使用异常而非返回码
如果使用返回码,调用者必须在调用之后即刻检查错误。
遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。
使用不可控异常
Java的第一个版本中引入可控异常时,看似一个极好的点子。每个方法的签名都列出它可能传递给调用者的异常,这些异常就是方法类型的一部分。
可控异常的代价就是违反了开放/闭合原则。如果你在方法中抛出可控异常,而catch语句在三个层级之上,你就得在catch语句和抛出异常处之间的每个方法签名中声明该异常。这意味着对软件中较低层级的修改,都将波及较高层级的签名。
如果你在编写一套关键代码库,则可控异常有时也会有用:你必须捕获异常。但对于一般的应用开发,其依赖成本要高于收益。
依调用者需要定义异常类
来看一个不太好的异常分类例子。下面的try-catch-finally语句是对某个第三方代码库的调用。它覆盖了该调用可能抛出的所有异常:
1 | ACMEPort port = new ACMEPort(12); |
语句包含了一大堆重复代码。在大多数异常处理中,不管真实原因如何,我们总是做相对标准的处理。
在本例中,既然知道我们所做的事不外如此,就可以通过打包调用API、确保它返回通用异常类型,从而简化代码。
1 | LocalPort port = new LocalPort(12); |
实际上,将第三方API打包是个良好的实践手段。当你打包一个第三方API,你就降低了对它的依赖:未来你可以不太痛苦地改用其他代码库。在你测试自己的代码时,打包也有助于模拟第三方调用。
打包的好处还在于你不必绑死在某个特定厂商的API设计上。你可以定义自己感觉舒服的API。在上例中,我们为port设备的错误定义了一个异常类型,然后发现这样能写出更整洁的代码。
对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。如果你想要捕获某个异常,并且放过其他异常,就使用不同的异常类。
别返回null值
返回null值,基本上是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查null值,应用程序就会失控。
别传递null值
除非API要求你向它传递null值,否则就要尽可能避免传递null值。
整洁代码是可读的,但也要强固。可读与强固并不冲突,如果将错误处理隔离开看待,独立于主要逻辑之外,就能写出强固而整洁的代码。做到这一步,我们就能单独处理它,也极大地提升了代码的可维护性。