Clean Code系列之异常处理

简介: 先前已经对异常如何设计,如何实践异常都写了几篇阐述了。再一次从Clean Code角度来谈谈异常的使用。

先前已经对异常如何设计,如何实践异常都写了几篇阐述了。再一次从Clean Code角度来谈谈异常的使用。

1、使用异常替代返回错误码

为什么?是从函数的角度去考虑:

函数要么做什么事,要么回答什么事,但二者不可得兼。也就是修改某对象状态,或者是返回该对象的有关信息。也就是指令与询问分隔开来


boolean set(String attribute,String value);

该函数设置某个指定属性,如果成功,就返回true,如果不存在那个属性,就返回false。


if(set("website","zhuxingsheng.com")){
    //
}

但从读者角度考虑一下,它是在问websit属性值是否之前已经设置为zhuxingsheng.com,还是在问websit属性值是否成功设置为zhuxingsheng.com呢?从该行语句很难判断其含义,因为set是动词还是形容词并不清楚。

作者本意是,set是一个动词,但在if语句的上下文中,感觉它是一个形容词。该语句读起来像是在说“如果websit属性值之前已经被设置为zhuxingsheng.com”,而不是“设置websit属性值为zhuxingsheng.com,看看是否可行,然后...”。

要解决这个问题,可以将set函数重命名为setAndCheckIfExists,但这对提高if语句的可读性帮助不大。真正的解决方案是把指令与询问分隔开来,防止产生混淆:

if(attributeExists("website"){
    setAttribute("website","zhuxingsheng.com");
}

《领域服务是抛出异常还是返回错误码》[1],提到过如何编写返回错误码

if(deletePage(page)) == OK){
}

但这样,从指令式函数返回错误码,有些违反指令与询问分隔的规则。

虽然这儿没有像上面的示例一样,引起动词与形容词的混淆,却会导致更深层次嵌套结构


if(deletePage(page) == OK){
    if(deleteRefrence(page.name) == OK) {
        if (deleteKey(page.name.key()) == OK) {
            //
        } else {
          //  
        }
    } else {
        //
    }
} else {
    //
}

使用异常替代错误码,错误处理代码能从主路径代码中分离出来:


try {
    deletePage(page);
    deleteRefrence(page.name);
    deleteKey(page.name.key());
} catch (Exception e) {
}

抽离try/catch 代码块

try/catch代码块丑陋不堪,搞乱了代码结构,把错误处理与正常流程结构分离开来。


void delete(Page page) {
    try {
        deltePageAndAllReferences(page)
    } catch(Exception e) {
        log;
    }
}

正常流程结构:


void deletePageAndAllReferences(Page page) {
    deletePage(page);
    deleteRefrence(page.name);
    deleteKey(page.name.key());
}

这样子代码干净了些,而且函数只干一件事。错误处理就是一件事。

想要更简化一下try/catch代码块,可以使用vavr工具包中的Try类


 Try.of((page) -> deltePageAndAllReferences(page)).onFailure(e -> log(e));

写法详情可观看小视频

网络异常,图片无法展示
|

https://mp.weixin.qq.com/s/vB4YpIFZkR1kPbvQ6TTGZQ


ErrorCode枚举类

返回的错误码,我们常会使用一个常量类或者枚举定义所有错误码。

当新增逻辑需要增加新错误码时,就会增加新代码,而且还要来修改这个错误码类。

这样的类被称为依赖磁铁,当这个类修改时,其他所有类都需要重新编译和部署。

使用异常类代替错误码,新异常可以从异常类派生出来,而无须重新编译或重新部署。

2、使用未检查异常

在之前的异常文章中,提到检查异常有很强的穿透力,当类调用链路长,在底层方法上增加新检查异常就会导致上层所有方法修改声明,有点违反OCP。

3、异常防腐

在DDD中有防腐层的概念,通过防腐层去隔离两个界限上下文的变化。

异常也有类似的情况。

当调用第三方API时,会需要处理异常情况。

ThirdPartAPI third = new ThirdPartAPI();
try {
    third.open();
} catch (Third1Exception e) {
    //
} catch (Thrid2Exception e) {
    //
} catch (Third3Exception e) {
    //
} finally {
}

首先我们需要打包这个第三方API,降低对它的依赖;也不必绑死在某一特定供应商API上,定义自己的API还要抽象异常

class ThirdPartService {
    public void open() {
        try {
            third.open();
        } catch (Third1Exception e) {
            //
            throw new SelfException(e);
        } catch (Thrid2Exception e) {
            //
            throw new SelfException(e);
        } catch (Third3Exception e) {
            //
            throw new SelfException(e);
        } finally {
        }
    }
}

上面代码,定义了抽象的ThirdPartSevice,并且抽象出SelfException。

总结

经过上面的三种手法,可以让代码在处理异常时,更加整洁。

References

[1] 《领域服务是抛出异常还是返回错误码》: https://www.zhuxingsheng.com/blog/does-the-domain-service-throw-an-exception-or-return-an-error-code.html


目录
相关文章
|
Android开发
XAMRAIN的INSTALL_FAILED_NO_MATCHING_ABIS错误处理
错误提示是:INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-11 解决方法: 打开xamarin .
1495 0
|
21天前
|
前端开发 开发者
【翻译】再见, Clean Code!
该文章源自React核心开发者Dan的博客《再见,清洁代码》。文中讲述了一个场景,作者的同事完成了一个功能,虽然代码运行正常但显得冗余。作者试图通过重构消除重复,使代码更整洁,但第二天被上司要求恢复原状。作者后来认识到,追求“清洁代码”是成长过程中的一个阶段,虽然抽象和消除重复有其价值,但应考虑代码的可维护性和团队协作。真正的目标不是绝对的“清洁”,而是适应变化和良好沟通。
24 0
|
21天前
|
机器学习/深度学习 Java Android开发
记录一个Flutter运行的异常FAILURE: Build failed with an exception. What went wrong: A problem occurred config
记录一个Flutter运行的异常FAILURE: Build failed with an exception. What went wrong: A problem occurred config
64 0
|
Java Maven
未解决:maven:Fatal error compiling: 无效的标记: -arg
未解决:maven:Fatal error compiling: 无效的标记: -arg
103 0
解决办法:configure: error: C compiler cannot create executables错误
解决办法:configure: error: C compiler cannot create executables错误
454 0
编译OpenJDK12:test_json.cpp error C2143 语法错误
编译OpenJDK12:test_json.cpp error C2143 语法错误
64 0
|
编译器 PHP C++
【VS Code 神奇小插件】Code Runner
【VS Code 神奇小插件】Code Runner
557 0
【VS Code 神奇小插件】Code Runner
首次运行Flutter失败报错(Finished with error: Gradle task assembleDebug failed with exit code 1)
首次运行Flutter失败报错(Finished with error: Gradle task assembleDebug failed with exit code 1)
315 0
首次运行Flutter失败报错(Finished with error: Gradle task assembleDebug failed with exit code 1)
|
前端开发 数据可视化 Linux
VS Code 常用的一些命令
VS Code 常用的一些命令
|
Ubuntu Shell
脚本执行常见错误:build.sh: caller: not found
从Ubuntu 6.10开始,默认使用dash(theDebian Almquist Shell)而不是bash(the GNUBourne-Again Shell)但Login Shell还是bash. 原因是dash更快、更高效,而且它符合POSIX规范。Ubuntu在启动的时候会运行很多shell脚本,使用dash可以加快启动速度。
169 0

热门文章

最新文章