4

When calling some function in a Python application, the function often calls functions deeper down which again call functions deeper down, etc. It is easy to unknowlingly pass a bad value to the top-level function which will cause a failure further down. Naturally, the parameters should be checked and exceptions raised with helpful error messages. But where should this ideally be done, and why?

  1. As high up as possible (error messages are probably easier to understand since they're closer to the user)
  2. As low down as possible (exception messages are closer to where the errors would actually occur, and the checks may be simpler since they probably don't depend on some obscure combination of input values, but the messages and variable names might be so far removed from the user that the only way of knowing what actually must be changed in the top-level call is to inspect the source from top to bottom)
  3. Both
  • 1
    There have been tons of resources and discussions how exceptions should be used and handled. And I have yet to see any kind of agreement. So I'm voting to close as "primarily opinion-based". – Euphoric Oct 2 '15 at 11:36
  • One could make the argument that an exception allows the code flow to "go to" the proper place for handling it. – user22815 Oct 2 '15 at 12:28
5

Throw an exception the moment that you know your code isn't going to be able to recover from an error condition on its own (i.e. without user intervention).

Example: Your code determines that a supplied path to a disk file does not exist, and the code cannot proceed without a good path. Throw at that moment, wherever it happens in your code.

Always include in the error message enough information to diagnose the problem. Preserve the call stack; you'll need that to get back to the place in the code where the exception was thrown. The call stack will tell you not only where the throw occurred, but every function or method call that took place up to the moment of the throw.

The whole point of exception handling is to provide the ability to throw an exception anywhere an unrecoverable error condition happens.

  • 1
    Always include in the error message enough information to diagnose the problem. Well, that's the problem - if you validate/throw exceptions far down in more generic helper functions, then you are often unable to provide helpful error messages (at least I've been completely unable to ascertain from many of the error messages in numpy and pandas what I did wrong "up top"). The call stack helps, but you don't see the values of the parameters in the function calls, and the variable names in generic helper functions may be less than helpful. – cmeeren Oct 2 '15 at 7:45
  • 1
    Can you provide an example of an error condition that you can't recover from which you would be unable to diagnose from a simple but informative exception message, a call stack, and a debugger? – Robert Harvey Oct 2 '15 at 7:58
  • 1
    For example, passing some wrong array shape (or a bad combination of such) to a top-level function may give an unhelpful IndexError some steps lower down. The IndexError may be the most specific error the lower-level function can give, but it's not helpful for me at all since it is buried deep beneath several layers of implementation and doesn't describe what I did wrong or have to fix. – cmeeren Oct 2 '15 at 8:01
  • 3
    Then figure out what conditions in your top-level code will cause the IndexError, and prevent those conditions from happening. IndexError is not an unforseen condition; it is a bug. – Robert Harvey Oct 2 '15 at 8:06
  • I'd add that it is quite impossible to foresee all error conditions and that unhelpful exceptions from deeply nested (helper) functions invariably pop up to the surface. See the popularity of many SO questions with a cryptic error message (my top post included). @RobertHarvey: you should really add your latest comment to the answer itself as it's a real complement. – user44761 Oct 2 '15 at 8:21
1

Python has 2 coding styles:

  1. Easier to ask for forgiveness than permission (EAFP): This coding style is based on head leap and try. So you use a lot of try-except blocks instead of if statements

    try:
        ham = spam.eggs
    except AttributeError:
        handle_error()
    
  2. Look before you leap (LBYL): This method is preferred in languages like C and Java.

    if hasattr(spam, 'eggs'):
        ham = spam.eggs
    else:
        handle_error() 
    

    That is not the Pythonic way for some developers and the docs explains some key points:

    In a multi-threaded environment, the LBYL approach can risk introducing a race condition between “the looking” and “the leaping”. For example, the code, if key in mapping: return mapping[key] can fail if another thread removes key from mapping after the test, but before the lookup. This issue can be solved with locks or by using the EAFP approach.

    Java language is the contrast of Python in this manner

    (In Java) The moral of this story is simple:exceptions are, as their name impllies, to be used only for exceptional conditions; they should never be used for ordinary control flow.

This answer has a good demonstration on when to use EAFP and when not.

If you are using python, it is normal to use exception instead of if-conditions and handle the situation according to the exception you catch. That will make you use exception on every level of the code. So that leads you to the answer 3, or the option 4) any level that you can use. If execution time is very important, than you can check above SO question.

0

Short answer is "Both".
You might want to trap "errors" early on, throwing out bad data (although whether that's an "Exception" is debatable). You definitely want to throw exception when things go "badly wrong", and that tends to be lower down in the layers of code.

A question to consider before throwing an Exception - "Then what?" If your code throws an exception, what is someone else's code going to do about it?

This is why there are lots of Exception classes, rather than just an "Error" object that happens to have some [arbitrary] error "Number" associated with it.
If a piece of code can catch your exception, analyse what went wrong (perhaps from what's passed in the Exception) and then recover from that the situation, all without the User knowing anything happened at all, then you're on to a Winner. Use specific Exception classes that can be caught and handled separately.

If all you ever want to do is dump the exception into a log file (never show the whole thing on screen!) then re-using the supplied, basic Exception classes is probably all you'll ever need.

-2

The answer is "3. Both." Every layer offers a specific interface, and this interface has specific constraints. These constraints should always be enforced by means of validation. One layer is not supposed to do another layer's validation, nor rely on another layer to do its validation for it. If you move a layer into a different system, it should still be able to function.

All unexpected exceptions (exceptions about things that normally should not happen) indicate a bug in the code that threw the exception, except "InvalidArgumentException", which indicates a bug in the code that called the code that threw the exception.

So, if layer A invokes layer B, and layer A contains a bug causing it to pass wrong information to layer B, but layer B does not do its own validation, then layer B will throw some "FailedToDoWhatYouAskedMeToDo" exception, at which point you will naturally start looking for the bug in layer B. If layer B does its own validation, then layer B will throw an "InvalidArgumentException" instead, thus pointing you to look for the bug in layer A.

So, if you do not have every layer do its own validation, then you will be constantly confused about where a bug is. You will often have one layer which is throwing an exception for a bug which exists in another layer, which is another way of saying that you may have a bug somewhere and expect a different layer to find it. This is chaos, and to be avoided at all costs.

There is a subtle point which is perhaps worth mentioning in the case of Python, because as far as I know, Python is a bit inconvenienced with respect to distinguishing public vs. private stuff. Normally, you may avoid duplication of validation within a specific layer, by having only public functions do validation. In other words, private functions of a layer do not need to be repeating validation that has already been done by public functions. But if all functions are public, then all of them need to do validation.

  • 1
    "Invalid Path" is not a bug in the code, unless by that you mean that the code should have checked for that in the first place. It might still have been a valid path up to the moment that it wasn't because some user deleted a folder... Some error conditions are unforseeable. – Robert Harvey Oct 2 '15 at 8:07
  • @RobertHarvey Generally, a subsystem which performs file operations will publicize the fact that it may at any moment throw an "I/O exception", a subclass of which is an "Invalid Path" exception. If the subsystem does not publicize this, and an "Invalid Path" exception still occurs, and goes unhandled, then this is a bug in the subsystem. – Mike Nakis Oct 2 '15 at 8:10
  • I don't think anyone is disagreeing with that. Sounds like you're advocating checked exceptions, something that I don't think Python has. Further, I think you're assuming a level of system/code "correctness" that can't be proven in all but the most trivial of code. – Robert Harvey Oct 2 '15 at 8:12
  • I specifically chose to speak of expected rather than checked exceptions precisely because I don't like checked exceptions very much. (Though I would rather say that I am undecided about them.) Of course, absolute correctness is not achievable, but it is worth always striving to get as close to as possible. And having a plan which is geared towards it, rather than against it, generally helps. – Mike Nakis Oct 2 '15 at 8:29
  • Also, on that topic, it is worth noting that IllegalArgumentException is an interesting case, because it is always publicized, (if the documentation is complete,) but it is never really an expected exception, because it should not happen. And it could never be a checked exception, because it would be a terrible nuisance. – Mike Nakis Oct 2 '15 at 8:34

Not the answer you're looking for? Browse other questions tagged or ask your own question.