很多编程语言有异常处理的语法,有时用起来非常方便。不过什么时候使用,该捕获什么异常,捕获后如何处理,什么时候扩散此异常,尤其是在函数,类,模块,主循环/函数中分别如何处理,有时候很费思量。这里以python为例,总结一下个人想法,抛砖引玉。
基本语法
异常处理的语法用好了非常方便。一个原因是和日常思考的过程,以及定义需求的思路类似。通常我们梳理需求,或者写功能时先写正常功能流程的需求,然后梳理异常情况下的需求,对应到代码就是正常功能代码和异常处理的代码。而不用正常流程的每个函数调用都判断一下返回值,处理一下异常情况。
基本语法如下:
try:
expression
…
expression
except ErrorA:
handle_ErrorA
except (ErrorB, ErrorC) as err:
handle_ErrorBC
except:
expression run on any other exceptions.
else:
expression run on no exception
finally:
expression run anyway
什么时候捕获异常
个人倾向于在一段处理逻辑后针对不同的异常进行处理,保障程序异常情况下的正常执行。处理逻辑的范围,要看异常处理代码的恢复范围。为了代码可读性,不建议太小,比如一个函数一个异常处理。比如:
def handle_data():
Open database
Read data from the database
Handle the data
Close the database
可以在这段功能外围整体进行异常处理,而不用每个处理分别进行异常处理。
也不建议外层的函数中处理几层以内的函数中的异常;
比如处理数据中调用了验证的函数,验证函数可能从另一个数据源获取数据。验证函数从另一个数据源获取数据的异常就应该在内层函数中处理,而不是在外层函数中处理。
跨越类或模块建议在把内部实现过程中遇到的异常封装在类/模块内部,统一返回这个类/模块的异常。比如:一个读取数据的模块,内部使用远程数据库读取数据,读取过程中可能会产生socket的异常,这种异常就建议封装在内部,进行重连或者反馈数据无法获得。而不是抛出socket的异常,让外部使用的代码捕获它。外部代码即使捕获到也无法恢复这种异常情况。
建议不要在内层函数中捕获所有的异常。因为没有预期的恢复处理,会深藏bug或引起不可预期的行为。除非是长期运行的服务类程序,可以考虑在主循环中捕获所有的异常。即使捕获所有异常也建议输出异常信息以便排查。
异常的处理
尽量根据具体的异常处理,使程序能从异常中恢复继续正常运行。通常的情况是对熟悉的几种异常进行了处理。逻辑上每个系统调用都可能出错,我们不对每个函数调用做异常处理,并不代表这个函数不会发生异常。比如上面的handle_data函数,如果数据库打不开如何处理?持续打不开如何处理?写数据库时报数据错误如何处理?
另外我们对未处理的异常可以输出必要的信息。比如一个服务程序通常会有一个主循环,通常会捕获所有的其他异常,记录后,继续提供服务。sys.exc_info()可以获得异常的类,异常的参数和一个堆栈的对象。traceback.format_exc()可以获得整个对战。下面是个示例代码:
try:
raise Exception('spam', 'eggs')
except:
print(‘Except[%s]’ % sys.exc_info())
print('Exception[%s]' % traceback.format_exc())
显示如下:
Exception[(, Exception('spam', 'eggs'), )]
Exception[Traceback (most recent call last):
File “./test.py”, line 30, in test
raise Exception('spam', 'eggs')
Exception: ('spam', 'eggs')
]
不要忘记在异常处理中释放异常发生前分配的资源。通常在finally中处理,或者用with语句。
抛出异常
自己抛出的异常建议有自己的捕获此异常的代码处理。如果一系列自己定义的异常,可以定义自己的异常类。
有时候抛出异常和返回错误值,都可以。抛异常性能略差。个人推荐经常可能发生的,尽量用返回值。
其他
异常处理机制如果使用得当会极大的方便编程实现。上面的提到的一些建议并非普遍适用,有些场景可能其他方式更有效率。需要在熟悉后逐渐总结。
欢迎反馈,关注,转发,谢谢!
领取专属 10元无门槛券
私享最新 技术干货