抛出异常不是业务逻辑

一个奇怪的用法

我遇到过一个项目,使用 .net core 搭建的一套OA系统,搭建这个系统的架构师,选择将不合法的请求,或是失败的请求用 throw 抛出异常,然后再异常过滤器中打包成一个正常相应的数据,返回给前端。

例:服务器接受到客户端的请求,检查上传的参数的时候,发现少了一个参数,这个时候应该告诉客户端:你少给我了一个参数。在这个系统中,要求所有的工程师遇到这个种情况要抛出异常,并在异常信息里写明要返回给客户端的描述信息。就是:

if (参数少了一个)
{
    throw new Exception("前端你的参数少了一个");   
}

然后在异常过滤器中将异常信息包装成一个正常返回值,主动抛出的异常描述信息也在这个返回值中返回给前端。

现在请思考下这种设计有没有什么不妥?

这种方式完全违背了异常机制的初衷,将异常机制大材小用,而唯一的好处就是写起来短。

异常机制表示这不是一个正常的业务代码,怎么理解?异常的抛出会停止系统的所有接下来的操作,这是强制性的。为什么这么做?为什么要停下来?是因为抛出异常代表发生了一个不可预知的危险操作,代表发生了正常业务逻辑以外的事,如果继续执行下去,可能会发生无法挽回的危险。所以需要一个抛出的操作强制终止程序的执行。

写个除法器

举个简单的例子,你需要写一个方法来计算两个数相除的商,你可以很快地写下这个方法:

double Div(double n1, double n2)
{
    return n1 / n2;
}

那如果除数 n2 接收到的是 0 ,怎么办?

在 .net 里除数为 0 的话会直接抛出异常,但如果让你来实现除法的话,你要这么应对这种情况呢?

可能有人会想到,多返回两个结果参数,一个用来指示计算是否成功,一个指示失败的原因,一共有三个参数,就像:

(bool, string, double) Div(double n1, double n2)
{
    //
}

这个可能是一个比较周全的方法,考虑到成功和失败的情况,但请思考一下,如果除数传进来是 0 ,这个方法会返回 (false, "除数不能为零", ??)。商的返回值应该是什么?有人会想:商的返回值是什么都可以,因为已经有 false 指示相除失败,也有说明失败原因的结果参数了。调用方只要先判断相除是否成功的参数就可以了。

没错,理想情况的确是这样,那么,你要怎么保证调用方一定会按照你的想法去使用这个方法呢?这个时候可能有人会想:那我管不着!别人要乱用我有什么办法!

对,这么想当然没问题,但我们是软件工程师,我们可以做的更好。你想一下,如果你的这个方法被使用在金融系统中,可能会造成多大的损失。无论你的返回值多么详尽,这仍然是一个不安全的方法。在调用方传递了一个不合法的值 0 后,仍然在商的结果位给了一个数字,而你是没有办法保证调用方不会那你的返回的错误结果商去当作正常的值使用,这可能会在金融系统中造成无法估量的损失,比如算少了你的工资。

这个时候我们应该怎么做,才能帮用户最大程度上减少损失?

抛出异常的妙用

我们要么确保一定给调用方返回一个正确的值,要么在计算不出正确的值时,什么都不返回。这就是需要抛出异常机制的时候了。

在接受到 0 作为除数时,直接抛出错误,阻止程序继续往下走。这样,在调用方给你一个错误的参数,你就阻止了调用方可能会做的胡搞瞎搞,从而迫使调用方检查他的参数。

尽管异常会造成一些性能损耗,但和可能发生的业务损失相比,这些性能损耗太微不足道了。

明白了吗,抛出异常的使用,不是作为正常的业务流程所使用,而是当发生你无法预计的不正常业务流程时,阻止他继续可能会造成的损失来使用的。像开头我提到了架构师搭建的项目,将抛出作为正常业务流程的一部分来使用,这使得抛出这个动作发生得十分频繁。因为你检查客户端的参数,这时一个正常的流程,你能知道那些参数时正确的,那些是不正确的,并知道要怎么提醒客户端。如果这里再检查参数不正确后就主动抛出,因为可能发生得十分,就会十分损耗新能。这是十分不可取的一种方式。

思考到这里,想一下,如果你调用的别人的代码抛出了异常,你要怎么办呢?是 try catch 捕获之后,当无事发生吗?这就浪费了别人抛出的一番苦心了,你要做的是,检查修改你的代码,看看参数是不是不正确,完善逻辑,以保证不会再诱发下一次同样的报错。如果你不能保证,并且也不能承担 try catch 后可能会发生的损失,那就让它报错。

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章