Where to put the error handing test?

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Tue Nov 24 17:45:37 EST 2009


On Tue, 24 Nov 2009 10:14:19 -0600, Peng Yu wrote:

> I'll still confused by the guideline that an error should be caught as
> early as possible.

I think you are confused by what that means. It means the error should be 
caught as early as possible in *time*, not in the function chain.

In Python, errors are always generated as soon as they occur. But imagine 
a system where you have a function that returns -1 on error and some 
integer on success. Then it is possible for that -1 error code to be 
passed along from one function to another to another to another before 
finally causing a fatal error, by which time you have no idea where it 
came from. That's what should be prohibited.



> Suppose I have the following call chain
> 
> f1() --> f2() --> f3() --> f4()
> 
> The input in f1() might cause an error in f4(). 

Assuming that none of the functions have side-effects, then f4 is the 
right place to catch the error. To do otherwise means that f1 needs to 
know all the possible things that can go wrong in f2, and f3, and f4.


> However, this error can
> of cause be caught by f1(), whenever I want to do so. In the worst case,
> I could duplicate the code of f2 and f3, and the test code in f4 to
> f1(), to catch the error in f1 rather than f4. But I don't think that
> this is what you mean.
> 
> Then the problem is where to put the test code more effectively. I would
> consider 'whether it is obvious to test the condition in the give
> function' as the guideline. However, it might be equal obvious to test
> the same thing two functions, for example, f1 and f4.

You're not a mathematician, are you?

For each function, define its domain: the values it must work with. 
Suppose f1 should return a result for (e.g.) all integers, but f2 only 
returns a result for *positive* integers.

Then it is very simple: f2 will raise an error if given zero or negative 
values, and so f1 must either:

* avoid that error by handling zero or negative separately; or
* catch the error and then handle zero or negative separately.

Simplified example:

def f2(x):
    if x > 0:
        return x+1
    raise ValueError


def f1(x):  # version 1
    if x > 0:
        return f2(x)
    else:
        return "something else"

# or

def f1(x):  # version 2
    try:
        return f2(x)
    except ValueError:
        return "something else"


Version two is the preferred way to do it, since that means f1 doesn't 
need to know what the domain of f2 is. But if f2 has side-effects 
(usually a bad idea) then version one is better.


But what if f1 and f2 have the same domain?

Then it is very simple: put the error checking wherever it makes sense. 
If f2 is public, put the error checking there, and then f1 can avoid 
duplicating the error checking.

But what if f1 has the more restrictive domain? Then put the error 
checking in f1.


-- 
Steven



More information about the Python-list mailing list