Exceptions in Programming

Exceptions in Programming

In programming we often need to notify the user of errors and invalid inputs. When we use functions we need to let the function caller know if an error happened during the processing of the function.

One way of doing this is to pass back special values that cannot occur in the normal operation of the program to mean an error happened. These are known as error codes.

This function calculates the area of a rectangle from its width and height. It does not allow either the width or height dimension to be zero or below, this is an error. However, it is still possible for the function to actually receive numbers than are zero or below. In this case we want to signal an error and not to perform the calculation.

To differentiate between a legitimate calculation result and an error, we can use negative numbers as error codes. No rectangle can legitimately have a negative area, therefore we can use -1 (or any negative number) to mean an error happened. If the user of the function receives a positive number back, they know it’s the correct calculation result. If they receive a negative number, they know it’s an error.

def rectangle_area(length, width):
    if length <= 0 or width <= 0:
        return -1
    return length * width

However, what about this case? This Python function calculates a customer’s account balance following a cash deposit into a bank account.

def deposit_cash( current_account_balance, cash_amount ):
    return current_account_balance+cash_amount

The cash amount deposited cannot be negative, as the customer cannot logically have deposited a negative amount of bank notes. However, it is technically possible for a negative number to be passed into the function as the cash_amount parameter as we cannot prohibit negative numbers in Python. Perhaps a cashier types in the wrong number on their bank terminal after counting the cash and this ends up as the input to the function. If this were to happen we want to raise an error.

We have a problem though. The function returns the new updated bank balance after the cash deposit is applied. We cannot use the previous method of using negative numbers to signal an error condition because a negative bank balance is quite possible, a situation many people are familiar with. The function could legitimately return a negative number if the cash deposited was insufficient to bring the customer’s already negative balance above zero.

We might use some absurd negative value to mean an error, say minus a billion, but what if a very large corporation uses the software? They could conceivably be a billion in debt. There’s no numbers that are really safe to use to mean an error happened.

We can solve this problem using an exception. A exception causes the function to return in a special way that signals an error. Using an exception means it is unambiguous that an error has happened and the error cannot be confused with a legitimate bank balance. Not all programming languages have exceptions, but Python does.

We can cause an exception in Python like this using the raise keyword.

def deposit_cash( current_account_balance, cash_amount ):
    if cash_amount < 0:
        raise Exception("ERROR: Negative amount of cash given.")
    return current_account_balance+cash_amount

So what happens if we run this with a negative cash_amount? As soon as the function gets to the raise keyword it stops running (it never calculates the balance) and immediately returns with the error. It prints out the text in the exception and then causes the program to fail and exit.

>>> deposit_cash( 1000, -50 )
...
Exception: ERROR: Negative amount of cash given.

So we got the error, but we don’t really want the program to fail and exit. We want the program to carry on running and allow the cashier to correct the amount of cash.

To find out if we got an exception without causing a failure, we use a structure called try and except (also known as try and catch). It looks like this in Python. Different variations are used in different programming languages:

try:
    updated_bank_balance = deposit_cash( 1000, -50 )
    print(f"New balance is {updated_bank_balance}")
except Exception as e:
    print(e)
    print("Please enter the amount of cash again.")

Now the program doesn’t fail. If an exception happens anywhere at all between try and except, the code that is after except is run. Otherwise the program continues normally to the next line and the code after except never happens.

So if we deposit a positive amount of cash we get this because no exception happens and the program continues on to the next line of code after the deposit and prints out the new updated balance.

New balance is 1050

But if we deposit a negative amount of cash we get:

ERROR: Negative amount of cash given.
Please enter the amount of cash again.

The Exception as e part means get the content of the exception that was returned from the function and put it into the variable e. The exception message is printed out via the subsequent print(e) line. Then the next line after that prints instructions that the error should be corrected by the user.

The fact that we need to specifically handle the exception using the try/except block otherwise the program will stop can be useful to avoid unfortunate bugs. If we passed back an error code, but forgot to handle it, the error code itself might be interpreted as a legitimate result from the function. For example if we had decided to use minus one billion as the error code for an incorrect deposit amount and a programmer forgot to look for this special value, someone might get their bank balance set to minus one billion! However, the exception method means the program would simply fail if the programmer forgot to check for the exception, which would be a safer outcome.

Exceptions can pass back extra information that error codes cannot convey. In the above example we were able to pass back a message explaining the exact problem using English words. Using error codes, we need to check documentation to find out what they mean.

It is possible in many languages to define different exceptions for different kinds of error and a function can pass back more than one kind of exception depending on what went wrong. A try/except block can check for multiple kinds of exceptions simultaneously.