Skip to content

Brilliant Coding Blog

Be Brilliant

Programming for failure – learning the basics

When code fails, the programmer has to consider what happens when that failure occurs. That includes knowing what the failure means and how to handle it. Exception handling is the sub-program inside of the program where we, as programmers, attempt to exert some control over the results of that failure.

Often we break program failures into two distinct types: errors and exceptions. Errors represent a significant failure in the execution of the program. In dynamic languages like Python, “errors” can include typos and other syntax problems and need to be fixed by the programmer. In compiled languages like Java, errors mean that an unrecoverable problem has occurred and the program should be allowed to crash. Exceptions, however, represent failures that we, as programmers, can anticipate. In fact we often want exceptions to happen so our program and react to the environment. We might even refer to exceptions as “operational errors”.
Here a couple of exception examples:
The program tries to write a file, but that file doesn’t exist, so our program needs to create it first.
The program attempts to make a network connection, but the connection times out, so our program needs to retry the connection.

In this post we’ll examine basic exception handling concepts. It’s important to note that more advanced designs exist especially when dealing with asynchronous call execution.

If you don’t know what errors can happen or don’t know what they mean, then your program cannot be correct except by accident.

Handling exceptions

A well designed program will avoid having very large routines in a single file. Instead, as part of the design, it’s common to break up the program into many files. This design approach results in having a number of different calls for the program’s execution. This “call stack” is straightforward when writing code for what *should* happen, but when failures happen, this is where our design requires some deeper thought..

Let’s take an example program that makes an HTTP call to a remote site with three classes: Main, ResponseHandler, and HttpConnection. This gives us three distinct execution call levels. Exceptions can happen at any level, but all exceptions can be handled at the top of the call stack due to the lower classes passing the exception back up the call stack. Handling generic exceptions at the top of our program’s “call stack” is a great way to start our error handling design. Eventually as we run and test our program we’ll build better handling routines that need to be located close to where the exception happens.

Often we will not write our program to explicitly handle *every* possible exception, in which case we should have a way to generically deal with “Unhandled” exceptions. The most common choice is to log these exceptions to a file. This type of logging is often referred to as an “exception stack trace” and we’ll talk more about this in the next section. By doing this we can identify new or unexpected unhandled exceptions, and then we can decide if we want to added specific handlers and where the best place to handle exceptions would be.

An example program where exceptions are handled or passed up the call stack

Let’s take a stroll through a simple program that makes an HTTP call to a remote server (you can find the full code example at the end of the post). We have all used programs that do this type of function (in fact your using one now!), this is the core of what a web browser does. But even in the simplest case, exception handling is much harder than you might think!

Our simple program has a simple call stack where the main function needs some data, so it calls a response handler, who then calls the HTTP connection class. It’s useful to note that our program execution travels “down” the call stack while our exceptions pass back “up” the same stack. At the lower level, Http Connection, can only retry the connection this only really handles low level network failures or timeouts. Depending on how frequently you are making connections, this exception often rarely happens. In this example we have an exception handler but you could also just let the exception pass up the next level in the call stack, the response handler.

At the response handler level we have a much broader set of design choices on how to deal with exceptions. For example if we get an exception parsing the data in the response, we can validate it or even send it to a different parser altogether (maybe it’s not HTML after all!). Additionally we might have logic which looks for missing or empty data, in this case we need to manually throw a new exception otherwise when we pass the data to the main function, it will *think* it has data, but in fact, it does not.

Finally we have worked our way back to the top of the call stack. At this point we should be calling a single exception handling routine, this routine handles all “unhandled” exceptions. In the case of our simple example we simply display the exception to the user.

Here’s a few more key exception design observations:
Often it’s useful to keep the exception handler close to where the exception occurs, this is so we can pass local information to the handler that will lose scope if we wait. Another key design concept is that we only attempt to catch exceptions that we want to handle.

Sometimes programs will catch an exception, but not actually handle it. In these cases we’ll want to “repeat” (“throw” in Java and “raise” in Python) the exception so that it gets passed up the next level in the call stack.

Many languages like Java and Python have a built-in exception hierarchy. Learn this hierarchy and use it, it’ll make building the exception handling part of the program easier.

By using exception handling as part of your program, you’ll have better control of program failures and be able to prevent unexpected behavior.

Tracing the call stack

Stack traces come in many forms!

Our discussion of programming for failure would not be complete without mentioning the stack trace. One common step our exception handlers should do at the top level of any call stack is to log the stack trace of the exception. A stack trace represents a “map” of the execution of our program at the time the exception happened. Stack traces aren’t pretty and are for programmers only. Without an in depth knowledge of the program, it’s easy for non programmers to make incorrect inferences based on a particular stack trace. Instead we want to log the stack trace to a file the programmer can review and at the same time display a reasonable message or action to the user.
In many programming languages the stack trace is displayed by default. So this is one of the first design considerations for our program; But as we saw if we use exception handlers, we can catch these exceptions before they make it back to the user and instead tell the user that a problem has occurred.

Wrapping up

Here are some additional dos and don’ts for exception handling and stack tracing:

  • DO: Keep any exception handling code simple.
  • DO: Allow external operational exceptions to fail fast and not wait on timers.
  • DO: Displaying messages from exceptions is an important part of the user interface design.
  • DO: Make any stack trace logging configurable.
  • DON’T: Keep stack trace logs forever.
  • DON’T: Truncate the stack trace.
  • DON’T: Automatically email the stack traces.
  • DON’T: Handle errors inside of your program. Remember, errors represent a significant failure of the environment or invalid code, so by definition are irrecoverable (handle the exceptions).
  • Our programs run in complex environments where many different failures can occur. As programmers, this failure should be included in the design of our program and not left to happenstance.

    Code Examples

    Java:

    
    public class Main {
        public static void main(String[] args) {
            try {
                Object obj = parseHttpResponse.getData();
                System.out.println(obj.toString());
            } catch (Exception e) {
                ExceptionHandler.displayError(e);
            }
        }
    }
    
    class ResponseHandler {
        public static Object getData() {
            try {
                Entity entity = HttpConnection.getConnection();
                if (entity == null) {
                    throw new NullEntityException();
                }
                return entity;
            } catch (HtmlParseException pe) {
                ExceptionHandler.validateHtml(pe, entity);
            }
        }
    }
    
    public class HttpConnection {
        public static final int MAXIMUM_RETRIES = 3;
        public static Entity getConnection() {
    
            try {
                response = Http.connect(method);
                return response;
            } catch (ConnectTimeoutException cte) {
                ExceptionHandler.connectRetry(cte, method, MAXIUM_RETRIES);
            }
        }
    }
    

    Python:

    
    class HttpConnection:
        MAXIMUM_RETRIES = 3
        def get_connection(self):
            try:
                response = http.connect(method)
                return response
            except ConnectTimeoutException as cte:
                handler.connect_retry(self, cte, method, self.MAXIMUM_RETRIES)
    
    class HttpResponse:
        def get_data(self):
            try:
                entity = HttpConnection.get_connection()
                if entity is None:
                    raise NoneEntityException('entity is none')
                return entity
            except HtmlParseException as pe:
                handler.validate_html(self, pe, entity)
    
    def main():
        try:
            obj = HttpResponse.get_data()
            print(str(obj)
        except Exception as e:
            handler.displayError(e)
    
    
Programming for failure – learning the basics