isaak.dev

Is it Easier to Ask Forgiveness than Permission or to Look Before You Leap?

06 Aug 2023 2 mins read
edit

The purpose of this article is to discuss two opposing approaches to writing code: EAFP and LBYL. There is no need for a long introduction, so let’s dive in.

The Problem

Suppose we have received a response from an API call, with following structure:

response = {
    "data": {
        "id": 241332,
        "recipient": {
            "city": "Palo Alto",
            "country": "US",
            "address": "1 Infinite Loop",
            "postal_code": "12345",
            "state": "CA",
            "name": "Tim Cook",
            "phone": "+1234567890",
            "email": "tcook@apple.com"
        },
        "cost": 1099.00,
        "currency": "USD",
        "name": "Google Pixel 7 Pro",
        "quantity": 1,
        "color": "Obsidian",
        "capacity": "512GB"
    }
}

Our goal is to retrieve the email of the recipient. We assume that any field may be missing to add complexity.

Look Before You Leap

The LBLY states that you should check for preconditions before performing any actions (like calling functions, accessing dictionary keys, or object attributes).

Just like this:

def get_user_email(response: dict) -> str | None:
    data = response.get('data')
    if data:
        recipient = data.get('recipient')
        if recipient:
            return recipient.get('email')
    return None

This approach is the most commonly used in programming, but it can also result in a lot of repetitive and unnecessary code, which can be challenging to maintain. So, why not attempt to access the key and handle the exception if it doesn’t exist? This is precisely where EAFP comes into play.

Easier to Ask Forgiveness than Permission

The EAFP states that it’s simpler to perform an action and then handle error, instead of checking all necessary conditions beforehand.

Example:

def get_user_email(response: dict) -> str | None:
    try:
        return response['data']['recipient']['email']
    except KeyError:
        return None

One essential thing to bear in mind is that we should always catch the most specific exception available. In this example, we are catching a KeyError exception, which is raised when a dictionary key is not found. If we catch a general Exception we may inadvertently catch exceptions that we weren’t expecting, leading to unexpected behavior.

Conclusion

Neither of these approaches is superior to the other. Both approaches have their pros and cons, and the choice of which one to use and when ultimately depends on the specific requirements and context of the program.

That’s all for today. See you next time!