4

I'm cleaning up an API library, and trying to figure out the best way to handle unhandled exceptions.

Right now, the library catches just about everything that could go wrong with the API -- credential errors, server errors, urllib2 errors, httplib errors, etc. There will be edge cases.

My current thinking is that 99% of library users don't care about the exception itself, they just care that the API call failed. Only developers would care about the exception.

That leads me to this solution :

class ApiError(Exception):
    pass

class ApiUnhandledError(ApiError):
    pass

Known issues with the API raise an ApiError or specific subclass.

Everything else raises an ApiUnhandledError , with the original error stashed in, which a user can either catch or ignore.

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

Does this sound like a good approach to ensuring the users just know a Pass/Fail , while developers can maintain a method of support ?

Update

Based on the consensus of best practices, I won't be trapping this.

The original goal was to allow people to do this:

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

in that example, the user only has to catch ApiError ( and optionally ApiUnhandledError or any of the other subclasses )

i thought this would be largely preferable to every api interaction having its own block :

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

when dealing with urllib2 , i've seem to discover a new exception every day. these exceptions tend to get very long and difficult to maintain.

4

1 回答 1

3

If your library raises an exception that you had not foreseen and you are not handling, be it so. It could be the sign of a library bug that would be ignored otherwise. If you can make the cause of the error explicit, it is ok to catch and relaunch (e.g. catch a socket.error and relaunch an AuthenticationFailedError in an authenticator method), but it is not fair to mask points of failure to library users (who are programmers themselves).

A user should not attempt to handle or silence errors directly coming from within the library (i.e. not part of your API -- roughly, not written or raised by you), since they are internal to that piece of code. If the library author forgot to handle them or to catch and relaunch a more specific one, it's a bug and it should be reported. If the function makes assumptions on the input (say, for speed), it should be clearly stated in the API reference and any violation is a user's fault.

In Python2 only a classic class or a new style class inheriting from BaseException can be raised, in Python3 classic classes are gone, so anything that your libraries may raise must be inherited from BaseException. Any exception that the user would normally want to handle (this excludes SystemExit, GeneratorExit, KeyboardInterrupt, etc., which make special cases) must inherit from Exception. Failure to do so will be reported when trying to raise it:

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
>>> 

That said, you don't need to wrap exceptions inside an UnhandledException container, just make all exceptions in your code inherit from Exception. Masking failures is bad practice (and you should not encourage it), but lazy user can still take advantage of the inheritance from Exception base class and catch all unhandled exceptions with this:

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

It is worth noticing that Python offers a warnings module for reporting "some condition in a program, where that condition (normally) doesn’t warrant raising an exception and terminating the program".

Speaking of application frameworks (not libraries), I like very much the Tornado approach: it will log all unhandled exceptions and continue if it the error is not critical. I think that this decision should be up to the final user.

于 2013-05-07T14:51:42.067 回答