“
阅读本文大概需要 3 分钟。
”写程序时,异常处理是在所难免的,但你有没有考虑过怎样让异常处理机制变得扩展性更好,让写法更优雅呢?
实例引入
比如写 Python 的时候,举个最简单的算术运算和文件写入的例子,代码如下:
def process(num1, num2, file):
result = num1 / num2
with open(file, 'w', encoding='utf-8') as f:
f.write(str(result))
这里我们定义了一个 process 方法,接收三个参数,前两个参数是 num1 和 num2,第三个参数是 file。这个方法会首先将 num1 除以 num2,然后把除法的结果写入到 file 文件里面。
好,就这么一个简单的操作,但是这个实现真的是漏洞百出:
•没有判断 num1、num2 的类型,如果不是数字类型,那会抛出 TypeError。•没有判断 num2 是不是 0,如果是 0,那么会抛出 ZeroDivisionError。•没有判断文件路径是否存在,如果是子文件夹下的路径,文件夹不存在的话,会抛出 FileNotFoundError。
一些异常测试用例如下:
process(1, 2, 'result/result.txt')
process(1, 0, 'result.txt')
process(1, [2], 'result.txt')
这些用例跑起来一定是会报错的。如果面试写出来这个代码,肯定就挂了。
当然最好的方式是通过一些判断条件把一些该处理的问题和判定都加上。
但这里我们为了说异常处理,如果把这几类的错误都进行异常处理的话,会写成什么样子呢?
由于 Python 的语法是有缩进的,所以我们可能会首先将这些代码缩进四个空格,然后外面包上一个 try 和 except,可能写成这个样子:
def process(num1, num2, file):
try:
result = num1 / num2
with open(file, 'w', encoding='utf-8') as f:
f.write(str(result))
except ZeroDivisionError:
print(f'{num2} can not be zero')
except FileNotFoundError:
print(f'file {file} not found')
except Exception as e:
print(f'exception, {e.args}')
这时候我们观察到了什么问题?
•代码一下子臃肿了起来,这里的异常处理都没有实现,仅仅是 print 了一些错误信息,但现在可以看到我们的异常处理代码可能比主逻辑还要多。•主逻辑代码整块被硬生生地缩进进去了,如果主逻辑代码比较多的话,那么会有大片大片的缩进。•如果再有相同的逻辑的代码,难道要再写一次 try except 这一坨代码吗?
可能很多人都在面临这样的困扰,觉得代码很难看但又不知道怎么修改。
当然上面的代码写法本身就不好,有几种改善的方案:
•本身这个场景不需要这么多异常处理,使用判断条件把一些意外情况处理掉就好。•异常处理本身就不应该这么写,每个功能区域应该和异常处理单独分开,另外各个逻辑模块建议再分方法解耦。•使用 retrying 模块检测异常并进行重试。•使用上下文管理器 raise_api_error 来声明异常处理。
但上面的一些解决方案其实还不能彻底解决代码复用和美观上的问题,比如某一类的异常处理我就统一在一个地方处理,另外我的任何代码都不想因为异常处理产生缩进。
那对于这样的问题,有没有解决方案呢?有。
下面我们来了解一个库,叫做 Merry。