The Case for Exceptions Part 3: Flow

In this post I would like to talk about what I consider to be one of the primary benefits for using exceptions to handle errors. That is, I believe that exceptions improve the flow of the code.

It is certainly true that using exceptions for error handling changes the way you have to think about a lot of code, and can make it more likely that error conditions will lead to faults such as leaked resources or inconsistent states. It is also the case that thinking about how exceptions will propagate can make writing correct code more complex. Both of these issues are about the exceptional code path - and I intend to address those in future posts.

In this post I want to talk about the effect exceptions have on the normal code path, the normal flow of control. This is important because most of the time execution will follow this path, so we need to consider the impact exceptions have on our ability to write this code, because this is often the most important code in the system, the code that actually executes the business logic or performs the functions the system was intended to perform.

It is my belief that using exceptions substantially increases the clarity of the normal path of execution. Error handling code is separated from the normal flow, so it is clear which code is intended to execute in each scenario. Typically, less error handling code is also required - meaning that more code can be seen at a time. Finally, when code cannot handle errors no specific error handling code is required - making it clear which code can handle errors and which is just forwarding them on.

Fork in the river

I feel this is most clear with a simple example. Some years ago I worked for a company called Pattern Analytics Research, developing an SDK for fusing results from multiple biometric algorithms. Like all good SDKs, with ours we provided complete examples illustrating how to use the SDK, including handling errors. We provided both a C and C++ interface (the C++ interface was a wrapper on the C interface that allowed typical C++ patterns such as RAII and exception handling to be used).

First, here is the sample code for the C interface:

// Copyright (c) Pattern Analytics Research Ltd.
// Used with permission

#include <stdio.h>
#include "par_fusion.h"

int main( int argc, char *argv[] )
{
    par_fusion_handle fuser = NULL;
    par_fusion_information_handle algorithm = NULL;
    int rv = 0;
    int i  = 0;
    float score = 0;
    double fused_score = 0;

    rv = par_fusion_init_fuser( &fuser );
    if( rv != PAR_OK )
    {
        fprintf(stderr,"Error: %s.\n", par_last_error_message());
        return 1;
    }

    rv = par_fusion_init_information( PAR_WARWICK_WARP_LIVESCAN_3_0_0, &algorithm );
    if( rv != PAR_OK )
    {
        fprintf(stderr,"Error: %s.\n", par_last_error_message());
        par_fusion_release_fuser(fuser);
        return 1;
    }

    for( i=1; i<argc; ++i )
    {
        if( sscanf(argv[i],"%f",&score) == 1 )
        {
            rv = par_fusion_add_score(fuser,algorithm,score);
            if( rv != PAR_OK )
            {
                fprintf(stderr,"Error: %s.\n", par_last_error_message());
                par_fusion_release_information(algorithm);
                par_fusion_release_fuser(fuser);
                return 1;
            }
        }
        else
        {
            fprintf(stderr,"Invalid score: %s.\n",argv[i]);
            par_fusion_release_information(algorithm);
            par_fusion_release_fuser(fuser);
            return 1;
        }
    }

    rv = par_fusion_fuse_scores(fuser,&fused_score);
    if( rv != PAR_OK )
    {
        fprintf(stderr,"Error: %s.\n", par_last_error_message());
        par_fusion_release_information(algorithm);
        par_fusion_release_fuser(fuser);
        return 1;
    }

    printf("%f\n",fused_score);

    par_fusion_release_information(algorithm);
    par_fusion_release_fuser(fuser);

    return 0;
}

Now, here is the sample code for the C++ interface:

// Copyright (c) Pattern Analytics Research Ltd.
// Used with permission

#include <sstream>
#include <iostream>
#include "par_fusion.h"

int main( int argc, char *argv[] )
{
    try
    {
        par::fusion::fuser fuser;
        par::fusion::fusion_information algorithm(PAR_WARWICK_WARP_LIVESCAN_3_0_0);

        for( int i=1; i<argc; ++i )
        {
            std::istringstream ss(argv[i]);
            ss.unsetf(std::ios::skipws);

            double score = 0;
            if( ss >> score )
            {
                fuser.add_score( algorithm, score );
            }
            else
            {
                std::cerr << "Invalid score: " << argv[i] << ".\n";
                return 1;
            }
        }

        std::cout << fuser.fused_score() << "\n";

        return 0;
    }
    catch( const par::exception& e )
    {
        std::cerr << "Error: " << e.what() << ".\n";
        return 1;
    }
}

I feel that this example illustrates quite clearly the difference. Firstly, the C++ example is 38 lines, vs. 65 for the C example. Given that they do exactly the same thing, this clearly makes it easier to read (especially since this is sample code). Obviously some of this can be attributed to RAII - but I’ve found that code bases that don’t generally permit exceptions become lax about RAII, so this isn’t an entirely unfair comparison.

Secondly, the error handling is confined to (almost only) one place in the C++ example. This makes it easier to see how errors are handled, and what happens when errors don’t occur.

Lastly, there is no duplication of error handling or clean-up. Again, this is a result of good C++ practices such as RAII and consolidation of common code - but exceptions make this easier.

So, in summary, exceptions make the normal flow of control easier to read, and makes it easier to write code that is easier to read. This is the code that is executed most often, and therefore likely to be most read both when being written, modified, and reviewed. It seems to me that this makes exceptions a clear benefit for the normal flow of control. In the next few posts we’ll look at how exceptions benefit object oriented programs, and then start to look at the cases that cause all the problems with exceptions - the times when an error actually occurs!

Posted on September 22, 2016
Want to see the edit history? Check the source on Github.

More in this series

|