前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Clean Code系列之异常处理

Clean Code系列之异常处理

作者头像
码农戏码
发布2022-11-18 09:43:59
3750
发布2022-11-18 09:43:59
举报
文章被收录于专栏:DDD

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

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

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

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

代码语言:javascript
复制
boolean set(String attribute,String value);

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

代码语言:javascript
复制
if(set("website","zhuxingsheng.com")){
    //
}

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

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

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

代码语言:javascript
复制
if(attributeExists("website"){
    setAttribute("website","zhuxingsheng.com");
}

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

代码语言:javascript
复制
if(deletePage(page)) == OK){

}

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

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

代码语言:javascript
复制
if(deletePage(page) == OK){
    if(deleteRefrence(page.name) == OK) {
        if (deleteKey(page.name.key()) == OK) {
            //
        } else {
          //  
        }
    } else {
        //
    }
} else {
    //
}

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

代码语言:javascript
复制
try {
    deletePage(page);
    deleteRefrence(page.name);
    deleteKey(page.name.key());
} catch (Exception e) {

}

抽离try/catch 代码块

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

代码语言:javascript
复制
void delete(Page page) {
    try {
        deltePageAndAllReferences(page)
    } catch(Exception e) {
        log;
    }
}

正常流程结构:

代码语言:javascript
复制
void deletePageAndAllReferences(Page page) {
    deletePage(page);
    deleteRefrence(page.name);
    deleteKey(page.name.key());
}

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

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

代码语言:javascript
复制
 Try.of((page) -> deltePageAndAllReferences(page)).onFailure(e -> log(e));

写法详情可观看小视频

ErrorCode枚举类

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

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

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

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

2、使用未检查异常

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

3、异常防腐

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

异常也有类似的情况。

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

代码语言:javascript
复制
ThirdPartAPI third = new ThirdPartAPI();

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

}

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

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农戏码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、使用异常替代返回错误码
  • 抽离try/catch 代码块
  • ErrorCode枚举类
  • 2、使用未检查异常
  • 3、异常防腐
  • 总结
    • References
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档