2013-05-07 182 views
3

我正在清理一个API库,并试图找出处理未处理的异常的最佳方法。如何处理未处理的异常?

现在,图书馆捕捉到所有可能出现错误的API - 凭证错误,服务器错误,urllib2错误,httplib错误等等。将会出现边缘情况。

我目前的想法是,99%的图书馆用户不关心异常本身,他们只关心API调用失败。只有开发人员会关心这个异常。

这使我这个解决方案:

class ApiError(Exception): 
    pass 

class ApiUnhandledError(ApiError): 
    pass 

与API的已知问题引发ApiError中或特定的子类。

其他一切都会引发一个ApiUnhandledError,原始错误被隐藏,用户可以捕获或忽略该错误。

try: 
    stuff 
except urllib2.UrlError , e : 
    raise ApiError(raised=e) 
except Exception as e : 
    raise ApiUnhandledError(raised=e) 

这听起来像是一种确保用户只知道通过/失败的好方法,而开发人员可以维护一种支持方法吗?

更新

基于最佳实践的共识,我不会捕捉这一点。

原来的目标是让人们能够做到这一点:

try: 
    stuff_a 
    other_stuff 
    even_more_stuff 

    api.proxy(url) 

    again_stuff 
    again_others 
except api.ApiUnhandledError , e : 
    handle error 
except api.ApiError , e : 
    handle error 
except Stuff , e : 
    other error 
except: 
    raise 

在这个例子中,用户只赶上ApiError中(以及可选ApiUnhandledError或任何其他子类)

我想这将有自己的块在很大程度上最好每个API互动:

try: 
    stuff_a 
    other_stuff 
    even_more_stuff 

    try: 
     api.proxy(url) 
    except api.ApiError , e : 
     handle error 
    except CatchSomething1 , e : 
     handle error 
    except CatchSomething2 , e : 
     handle error 
    except CatchSomething3 , e : 
     handle error 
    except CatchSomething4 , e : 
     handle error 
    except: 
     raise 

    again_stuff 
    again_others 
except Stuff , e : 
    other error 
except: 
    raise 

与urllib2的问题时,我已经似乎发现了新的除外离子每天。这些例外往往会变得很长,难以维持。

+1

您确定要使用此类机制吗?你只会用你的“假”来掩盖一个真正的例外。我只是留下未经处理的例外,因为它们是最具信息性的方式。如果你想将它们记录在某处或创建一个普通的动作,那就是另一回事了。 – Michal 2013-05-07 14:34:41

+0

我不确定我想要这样做。我担心的是有一些错误代码可能会从底层库中弹出。看看使用这个库的我自己的“消费者”代码,我不得不在自己的块中嵌入对这个库的调用,然后在它周围链接大量的异常处理来捕获这些错误。我试图在上游端口处理这个异常处理,这似乎是一种可能的方式来最大限度地减少大多数消费者的工作,同时仍然保留例外情况。 – 2013-05-07 14:42:18

回答

2

如果你的图书馆提出了一个你没有预见到但你没有处理的例外,那就这样吧。这可能是一个图书馆错误的迹象,否则将被忽略。如果可以明确错误的原因,那么可以捕获并重新启动(例如,捕获socket.error并在验证器方法中重新启动AuthenticationFailedError),但将图书馆用户的故障点掩盖是不公平的(程序员是谁他们自己)。

用户不应该尝试处理或消除直接来自库内部的错误(即,不是您的API的一部分 - 大致上,不是您写的或编写的),因为它们是内部的那段代码。如果图书馆作者忘记处理它们,或者抓住并重新启动一个更具体的作品,那么这是一个错误,应该报告。如果函数对输入做出假设(比如说,对于速度),应在API参考中明确声明,并且任何违规都是用户的错误。

在Python2中,只能继承从BaseException继承的经典类或新风格类,在Python3中,经典类不见了,因此您的库可能引发的任何事情都必须从BaseException继承。用户通常要处理的任何异常(这不包括SystemExit,GeneratorExit,KeyboardInterrupt等,这些都是特殊情况)必须从Exception继承。否则,将报告试图提高它的时候:

Python3:

>>> class Test: 
...  pass 
... 
>>> raise Test() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: exceptions must derive from BaseException 

Python2:

>>> class Test(object): 
...  pass 
... 
>>> raise Test() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: exceptions must be old-style classes or derived from BaseException, not Test 
>>> 

这就是说,你不需要包装一个UnhandledException容器内的异常,只需使代码中的所有例外都从Exception继承。屏蔽失败是不好的做法(和你不应该鼓励的话),但懒惰的用户仍然可以从Exception基类采取继承的优势,赶上这一切未处理的异常:

try: 
    yourapi.call_me() 
except APIKnownError as error: 
    report(error) 
except Exception as error: 
    pass 
    # Or whatever 

这是值得注意的是,python提供一个warnings模块用于报告“程序中的某些条件,其中该条件(通常)不保证引发异常并终止程序”。说到应用程序框架(不是库),我非常喜欢Tornado方法:它会记录所有未处理的异常,并在错误不重要时继续。我认为这个决定应该取决于最终用户。

+0

在你的例子中,我在思考这个问题:我得到一个'socket.error'异常,然后用'socket.error'的实例作为一个属性引发'ApiUnhandledError'。我不想隐藏失败点,我只是希望图书馆用户有一个“更容易”的时间来处理错误。如果他们关心潜在问题是什么,他们可以轻松检查 - API提出了一个异常,该异常存储原始的提升。由于这个库使用了非常讨厌的urllib2软件包,我将所有可能发生的随机边缘案例(除了9个显式捕获)整合到一个ApiUnhandledError中。 – 2013-05-07 15:20:11

+0

@JonathanVanasco,我不会在另一个内部包装一个异常,因为这会向用户公开一个内部依赖:如果你想使用自己的错误类型从'socket'切换到更高级的库,该怎么办?用户必须无需重写他/她的处理代码。我会提取相关信息并使用它们来构建您的异常。同时检查编辑。 – 2013-05-07 16:49:01

+0

Python 2中的用户异常应该从'Exception'派生,而不是'BaseException' http://docs.python.org/2/library/exceptions.html#exceptions.Exception – 2013-05-07 17:06:33