由于调用很多情况下都存在子调用、而子调用链上很可能某一环就抛出了异常,此文主要讲我是怎么看待和处理这种情况的
不好的做法 什么都不做 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def c(): raise Exception('xxx') def b(): # dong pre things c() # dong after things def a(): b() if __name__ == '__main__': a() """ 输出: Traceback (most recent call last): File "/.../tmp.py", line 14, in <module> a() File "/.../tmp.py", line 10, in a b() File "/.../tmp.py", line 6, in b c() File "/.../tmp.py", line 2, in c raise Exception('xxx') Exception: xxx """
1. 这样导致程序直接退出了、而得到的报错信息是程序报错的信息,这样是不能够提供给用户(非开发人员)的
2. 有可能在b环节里、调用c压根不是个重要的事情(假设是打日志到某日志收集平台)、那么由于c发生的异常导致整体让用户认为操作失败了是完全没有必要的
3. c真的应该raise Exception吗?这会导致所有其调用者都需要使用try ... catch ...来捕捉错误信息
所有子调用都统一返回result、error格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def c(): return None, Exception('xxx') def b(): # dong pre things c_res, err = c() # 若我(b)认为是关键步骤、就直接返回 None,'从b的角度转化为可读性更高的报错信息' # 否则继续往下走 # dong after things return 'sth', None def a(): b_resp, err = b() if __name__ == '__main__': a()
1. 优点:b能够处理上面提到的没必要因为不重要事情而退出的情况
2. 优点:a拿到b的可读性更好的报错信息,其可以根据自己对于b业务的重要性等判断来决定是否处理以及返回的错误信息(从a的角度理解是a最接近用户、最接近具体业务、其能返回可读性更高的信息、以及更能决定哪些步骤才是核心步骤; 从b的角度理解就是b只提供特定功能,此功能在调用b的不同调用者中的重要程度不同,对于b处理结果的看待方式应由调用者决定)
2. 缺点:从a的角度拿到的信息少了底层(c)的真实程序报错信息,不方便开发者来后续分析与处理
我希望做到的是若某次的调用链其中某一/几环出现了异常
尽量 不raise Exception从而避免自己其实是个不重要的功能却导致整体的调用失败
最后能返回给用户可读性非常高的异常信息
能够提供给开发者掌握到各个调用的异常详情信息以方便排查/修复
实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 """ 现在我的项目是将Ret实现到一个每个文件都应该导入的一个包里,并确保所有的返回都是Ret实例 这里为了方便写到一起 对比之前我习惯以result, error的返回方式,我觉得这种方式的优点会多不少(对于"核心思想") """ class Ret : def __init__ (self, result=None, error=[]) : self.result = result self._err = [] if not error else [error] def __setattr__ (self, key, value) : if key == 'error' : self._err.append(value) return return super().__setattr__(key, value) @property def error (self) : if self.result or not self._err: return None return self._err.__str__() def add_error (self, value) : self._err.append(value) def __bool__ (self) : return len(self._err) == 0 def __str__ (self) : if self: return f'SUC with result: {self.result} ' else : return f'FAIL with error: {self.error} ' def c () : ret = Ret() ret.error = 'c failed' return ret def b () : ret = c() if ret.error: pass ret.error = 'b failed' return ret def a () : ret = b() print(ret) print(ret.error) if __name__ == '__main__' : a() """ 输出: FAIL with error: ['c failed', 'b failed'] ['c failed', 'b failed'] """