从指令式函数返回错误码轻微违反了指令与询问分隔的规则。它鼓励了在if语句判断中把指令当作表达式使用。
- if (deletePage(page) == E_OK)
这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。当返回错误码时,就是在要求调用者立刻处理错误。
- if (deletePage(page) == E_OK) {
- if (registry.deleteReference(page.name) == E_OK) {
- if (configKeys.deleteKey(page.name.makeKey()) == E_OK){
- logger.log("page deleted");
- } else {
- logger.log("configKey not deleted");
- }
- } else {
- logger.log("deleteReference from registry failed");
- }
- } else {
- logger.log("delete failed");
- return E_ERROR;
- }
另一方面,如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化:
- try {
- deletePage(page);
- registry.deleteReference(page.name);
- configKeys.deleteKey(page.name.makeKey());
- }
- catch (Exception e) {
- logger.log(e.getMessage());
- }
Try/catch代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。
- public void delete(Page page) {
- try {
- deletePageAndAllReferences(page);
- }
- catch (Exception e) {
- logError(e);
- }
- }
- private void deletePageAndAllReferences(Page page) throws Exception {
- deletePage(page);
- registry.deleteReference(page.name);
- configKeys.deleteKey(page.name.makeKey());
- }
- private void logError(Exception e) {
- logger.log(e.getMessage());
- }
在上例中,delete函数只与错误处理有关。很容易理解然后就忽略掉。deletePageAndAllReference函数只与完全删除一个page有关。错误处理可以忽略掉。有了这样美妙的区隔,代码就更易于理解和修改了。
错误处理就是一件事
函数应该只做一件事。错误处理就是一件事。因此,处理错误的函数不该做其他事。这意味着(如上例所示)如果关键字try在某个函数中存在,它就该是这个函数的第一个单词,而且在catch/finally代码块后面也不该有其他内容。
错误依赖磁铁
返回错误码通常暗示某处有个类或是枚举,定义了所有错误码。
- public enum Error {
- OK,
- INVALID,
- NO_SUCH,
- LOCKED,
- OUT_OF_RESOURCES,
- WAITING_FOR_EVENT;
- }
这样的类就是一块依赖磁铁(dependency magnet);其他许多类都得导入和使用它。当Error枚举修改时,所有这些其他的类都需要重新编译和部署。 这对Error类造成了负面压力。程序员不愿增加新的错误代码,因为这样他们就得重新构建和部署所有东西。于是他们就复用旧的错误码,而不添加新的。
使用异常替代错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。