LBYL vs EAFP in Java?
Asked Answered
D

5

79

I was recently teaching myself Python and discovered the LBYL/EAFP idioms with regards to error checking before code execution. In Python, it seems the accepted style is EAFP, and it seems to work well with the language.

LBYL (Look Before You Leap):

def safe_divide_1(x, y):
    if y == 0:
        print "Divide-by-0 attempt detected"
        return None
    else:
        return x/y

EAFP (it's Easier to Ask Forgiveness than Permission):

def safe_divide_2(x, y):
    try:
        return x/y
    except ZeroDivisionError:  
        print "Divide-by-0 attempt detected"
        return None

My question is this: I had never even heard of using EAFP as the primary data validation construct, coming from a Java and C++ background. Is EAFP something that is wise to use in Java? Or is there too much overhead from exceptions? I know that there is only overhead when an exception is actually thrown, so I'm unsure as to why the simpler method of EAFP is not used. Is it just preference?

Disgust answered 1/1, 2009 at 9:50 Comment(0)
N
8

Personally, and I think this is backed up by convention, EAFP is never a good way to go. You can look at it as an equivalent to the following:

if (o != null)
    o.doSomething();
else
    // handle

as opposed to:

try {
    o.doSomething()
}
catch (NullPointerException npe) { 
    // handle
}

Moreover, consider the following:

if (a != null)
    if (b != null)
        if (c != null)
            a.getB().getC().doSomething();
        else
            // handle c null
    else
        // handle b null
else
    // handle a null

This may look a lot less elegant (and yes this is a crude example - bear with me), but it gives you much greater granularity in handling the error, as opposed to wrapping it all in a try-catch to get that NullPointerException, and then try to figure out where and why you got it.

The way I see it EAFP should never be used, except for rare situations. Also, since you raised the issue: yes, the try-catch block does incur some overhead even if the exception is not thrown.

Nitre answered 1/1, 2009 at 9:57 Comment(10)
This sort of EAFP depends in part on whether the exceptions you test for are going to occur very often. If they are unlikely, then EAFP is reasonable. If they are common, then LBYL may be better. The answer probably also depends on the exception handling paradigm available. In C, LBYL is necessary.Dolomite
a "common exception" is not an exception at all, so of course LBYL will be preferred in that case, don't you think?Illfounded
Saying "the try-catch block does incur some overhead even if the exception is not thrown" is like saying "the method definition does incur some overhead even if the method is not called". In other words, the overhead is negligible until the method/exception is invoked. Erickson explained it best when I asked about this.Candidate
I utterly and completely disagree. Logically, EAFP is a good way to do validation. Because if the check that throws the exception is already there anyway, additional check can only get out of sync and saves nothing. However due to comparably slow exceptions in C++, Java and C#, LBYL is needed when the failure is expected to happen but rarely.Marchant
don't nested try-catch blocks defeat the point of [supposedly] greater granularity of LBYL? It's just that the operations should be written in more lines, which is arguably also more readable.Postage
EAFP can also have better security properties (eliminating some race conditions) in multi-threaded environments.Cayser
EAFP is a good example of DRY. If you use logic apart from exceptions, you (a) may screen for conditions that aren't in fact errors because your checking code is out of sync with the service or library you're calling; and (b) vice versa, your checking code may not handle conditions that are in fact errors -- and you'll end up having to use exceptions anyway. If the underlying service or library will use exceptions when it (authoritatively) encounters an error, then you should use exceptions. This plus the race conditions issue described by Jonathan Leffler suggest EAFP is the way to go.Ldopa
EAFP, similar to what mehaase is saying, is useful in situations where you have no control over the error being thrown - e.g. when scraping a website.Sousa
you could still do B b=null; C c=null; try{b=a.getB();try{c=b.getC();}catch(NullPointerException){/*stuff2*/}}catch(NullPointerException){/*stuff*/}Alinealinna
I know this is super old. But I think it's good to weigh in as newer people come to this question. EAFP is more practical and useful from a concurrency and mutli-threading perspective. Allowing increased atomicity (while not completely achieving it) and reduction of race conditions. EAFP should be used in distributed computing as necessary, applied appropriately. I personally am of the belief both should be used always, and neither one is wrong per se and is meant to be used for a specific situation.Thrown
D
146

If you are accessing files, EAFP is more reliable than LBYL, because the operations involved in LBYL are not atomic, and the file system might change between the time you look and the time you leap. Actually, the standard name is TOCTOU - Time of Check, Time of Use; bugs caused by inaccurate checking are TOCTOU bugs.

Consider creating a temporary file that must have a unique name. The best way to find out whether the chosen file name exists yet is to try creating it - making sure you use options to ensure that your operation fails if the file does already exist (in POSIX/Unix terms, the O_EXCL flag to open()). If you try to test whether the file already exists (probably using access()), then between the time when that says "No" and the time you try to create the file, someone or something else may have created the file.

Conversely, suppose that you try to read an existing file. Your check that the file exists (LBYL) may say "it is there", but when you actually open it, you find "it is not there".

In both these cases, you have to check the final operation - and the LBYL didn't automatically help.

(If you are messing with SUID or SGID programs, access() asks a different question; it may be relevant to LBYL, but the code still has to take into account the possibility of failure.)

Dolomite answered 1/1, 2009 at 17:52 Comment(2)
Great example, Jonathan. This probably makes a lot of sense to java developers who have dealt with concurrent programming and the double-checked lock idiom.Mezzorelievo
This is a good point but I don't believe this really has to do with LBYL vs EAFP. Any time you open a file for instance you are basically doing the same thing, one operation that returns an error, you're not checking if you can open it before actually doing it. Such code I argue can be LBYL: multiple function calls, each function call can fail and each is checked on the spot. TOCTOU is a more general problem. Also, your answer addresses a correctness concern whereas I believe the question was about instances where both solutions would be correct.Centurial
B
59

In addition to the relative cost of exceptions in Python and Java, keep in mind that there's a difference in philosophy / attitude between them. Java tries to be very strict about types (and everything else), requiring explicit, detailed declarations of class/method signatures. It assumes that you should know, at any point, exactly what type of object you're using and what it is capable of doing. In contrast, Python's "duck typing" means that you don't know for sure (and shouldn't care) what the manifest type of an object is, you only need to care that it quacks when you ask it to. In this kind of permissive environment, the only sane attitude is to presume that things will work, but be ready to deal with the consequences if they don't. Java's natural restrictiveness doesn't fit well with such a casual approach. (This is not intended to disparage either approach or language, but rather to say that these attitudes are part of each language's idiom, and copying idioms between different languages can often lead to awkwardness and poor communication...)

Beatrix answered 2/1, 2009 at 23:31 Comment(3)
Also, oranlooney.com/lbyl-vs-eafp provides a good set of pros and cons for each approach.Maundy
I disagree that the loose typing in Python makes it more or less sensible to use EAFP. When you ask for forgiveness, you ask to be forgiven for expected cases. For example, if a "cancel" method is going to cancel an object, we will catch for the exceptions we expect to come back. We expect cancel may fail because the object has active relationships that prevent it from being canceled, and we want to communicate that back to the UI. If the cancel method fails because of some unforseen divide by zero scenerio, we want it to fail normally like any other error in the program.Ringler
the link doesn't work anymore so someone could read the article here instead web.archive.org/web/20161208191318/http://www.oranlooney.com/…Fylfot
E
12

Exceptions are handled more efficiently in Python than in Java, which is at least partly why you see that construct in Python. In Java, it's more inefficient (in terms of performance) to use exceptions in that way.

Enclose answered 1/1, 2009 at 14:30 Comment(3)
mipadi, do you have any insight as to how python accomplishes this?Thormora
@Thormora I had the same question, and found it here: #598657Maundy
@Thormora most of the discussion centers around LBYL vs EAFP, but one of the answers linked to how exceptions are actually implemented in CPython: docs.python.org/c-api/intro.html#exceptionsMaundy
D
10

Consider these code snippets:

def int_or_default(x, default=0):
    if x.isdigit():
        return int(x)
    else:
        return default

def int_or_default(x, default=0):
    try:
        return int(x)
    except ValueError:
        return default

They both look correct, right? But one of them isn't.

The former, using LBYL, fails because of a subtle distinction between isdigit and isdecimal; when called with the string "①²³🄅₅", it will throw an error rather than correctly return the default value.

The later, using EAFTP, results in correct handling, by definition. There is no scope for a behavioural mismatch, because the code that needs the requirement is the code that asserts that requirement.

Using LBYL means taking internal logic and copying them into every call-site. Rather than having one canonical encoding of your requirements, you get a free chance to mess up every single time you call the function.

It's worth noting that EAFTP isn't about exceptions, and Java code especially should not be using exceptions pervasively. It is about giving the right job to the right block of code. As an example, using Optional return values is a perfectly valid way of writing EAFTP code, and is far more effective for ensuring correctness than LBYL.

Darmit answered 28/4, 2017 at 18:26 Comment(5)
That's just because in the first case you did not choose a suitable function for your case. Had you chosen a function that checks exactly what you want, the problem that's given in your argument would not exist.Porett
@Silidron All programming errors are from not writing exactly correct code! That's the point! Why have the opportunity to make this mistake, every time the function is called from every programmer who uses it, when the function is already perfectly happy to integrate this check?Darmit
Well there's an opportunity to forget throwing ValueError inside the int() casting, and then the second example breaks too.Porett
@Porett Yes, but that opportunity happens once, inside the code that's already responsible for knowing and dealing with all the particular details of int parsing. Versus having that opportunity at every call site, by people with potentially much less specialized knowledge about these details. The person writing int will naturally know the difference between digits and decimals, because that's their responsibility, and the code is already parsing it. Most people calling int won't, because they're solving some other problem, and they just want it to work and get out of the way.Darmit
@Porett LBYL is fundamentally broken. You're doing action X to check if action Y will work. But action X is not action Y, so it can't definitively tell you action Y will or won't work - that's in addition to LBYL also being broken because it's a TOCTOU bug. This question is a perfect example of the fundamental flaw in LBYL.Rashad
N
8

Personally, and I think this is backed up by convention, EAFP is never a good way to go. You can look at it as an equivalent to the following:

if (o != null)
    o.doSomething();
else
    // handle

as opposed to:

try {
    o.doSomething()
}
catch (NullPointerException npe) { 
    // handle
}

Moreover, consider the following:

if (a != null)
    if (b != null)
        if (c != null)
            a.getB().getC().doSomething();
        else
            // handle c null
    else
        // handle b null
else
    // handle a null

This may look a lot less elegant (and yes this is a crude example - bear with me), but it gives you much greater granularity in handling the error, as opposed to wrapping it all in a try-catch to get that NullPointerException, and then try to figure out where and why you got it.

The way I see it EAFP should never be used, except for rare situations. Also, since you raised the issue: yes, the try-catch block does incur some overhead even if the exception is not thrown.

Nitre answered 1/1, 2009 at 9:57 Comment(10)
This sort of EAFP depends in part on whether the exceptions you test for are going to occur very often. If they are unlikely, then EAFP is reasonable. If they are common, then LBYL may be better. The answer probably also depends on the exception handling paradigm available. In C, LBYL is necessary.Dolomite
a "common exception" is not an exception at all, so of course LBYL will be preferred in that case, don't you think?Illfounded
Saying "the try-catch block does incur some overhead even if the exception is not thrown" is like saying "the method definition does incur some overhead even if the method is not called". In other words, the overhead is negligible until the method/exception is invoked. Erickson explained it best when I asked about this.Candidate
I utterly and completely disagree. Logically, EAFP is a good way to do validation. Because if the check that throws the exception is already there anyway, additional check can only get out of sync and saves nothing. However due to comparably slow exceptions in C++, Java and C#, LBYL is needed when the failure is expected to happen but rarely.Marchant
don't nested try-catch blocks defeat the point of [supposedly] greater granularity of LBYL? It's just that the operations should be written in more lines, which is arguably also more readable.Postage
EAFP can also have better security properties (eliminating some race conditions) in multi-threaded environments.Cayser
EAFP is a good example of DRY. If you use logic apart from exceptions, you (a) may screen for conditions that aren't in fact errors because your checking code is out of sync with the service or library you're calling; and (b) vice versa, your checking code may not handle conditions that are in fact errors -- and you'll end up having to use exceptions anyway. If the underlying service or library will use exceptions when it (authoritatively) encounters an error, then you should use exceptions. This plus the race conditions issue described by Jonathan Leffler suggest EAFP is the way to go.Ldopa
EAFP, similar to what mehaase is saying, is useful in situations where you have no control over the error being thrown - e.g. when scraping a website.Sousa
you could still do B b=null; C c=null; try{b=a.getB();try{c=b.getC();}catch(NullPointerException){/*stuff2*/}}catch(NullPointerException){/*stuff*/}Alinealinna
I know this is super old. But I think it's good to weigh in as newer people come to this question. EAFP is more practical and useful from a concurrency and mutli-threading perspective. Allowing increased atomicity (while not completely achieving it) and reduction of race conditions. EAFP should be used in distributed computing as necessary, applied appropriately. I personally am of the belief both should be used always, and neither one is wrong per se and is meant to be used for a specific situation.Thrown

© 2022 - 2024 — McMap. All rights reserved.