Exceptional Exceptions, Demystified - Part 2

Exceptional Exceptions, Demystified - Part 2

< Part 1: Understanding Exceptions

Part II: Handling Exceptional Exceptions

In part 1, we took a look at what exceptions are in programming, and the differences between unexceptional and exceptional exceptions.  Your organization should strongly consider formalizing the idea that only exceptional exceptions should be raised/thrown into your standard development practices and policies.

The ideas introduced in this part of the article are general good practice for any software engineer, and can serve as a good starting point for a simple exception handling policy for our organization.

Maintain Cohesion and Encapsulation

Cohesion refers to the level at which the members of an entity belong together and provide a uniform interface. Encapsulation refers to the level at which an object or entity hides its internal members and workings. Both are critical components of clean code. These concepts furthermore provide a strong foundation for policies on handling exceptional exceptions.

As an example: a Java function that has a signature similar to the one below, has very poor cohesion and encapsulation because it exposes implementation details at a level that is both confusing and unnecessary:

A software engineer having to implement this method would be forced to handle the FileNotFoundException which has no clear relation to getting the employee history records (cohesion), and exposes details of the inner workings of the function (encapsulation). Additionally, if the system were updated to retrieve the information from another source, the FileNotFoundException would likely change to a different exception, causing rippling changes throughout the code.  A more practical solution is to write the function to try to recover from the FileNotFoundException gracefully, or if it cannot, to throw a clearer, more cohesive exception, such as a custom exception called EmployeeInfoUnavailableException:

With a more relevant exception, cohesion and encapsulation are preserved, allowing the software engineer to more appropriately handle the exception. Furthermore, if the inner implementation is changed, as mentioned before, the changes are contained to the encapsulated code, and do not ripple through the program needlessly.

Avoid Exception Farming

Imagine a farmer planting seeds in his fertile valley field, so that later, he can harvest what he planted, multiplied. Exceptions are much the same: No truly exceptional exception can safely be buried: else you will get a bountiful harvest of exceptions. Assuming that you have implemented only truly exceptional exceptions, there is a valid reason for each and every exception. The exceptions are being thrown to warn you, the software engineer of trouble. An exception buried (planted) and left will grow and multiply!

Below is an example of exception farming in C#:

The following observations are made of the snippet above:

  1. The function DoSomething wraps a call to DoSomethingElse, which may throw exceptions, with a try/catch block.
  2. Any exception thrown by DoSomethingElse is caught, and ignorantly buried, doing absolutely nothing with it. This exception is likely to come back later with greater ramifications.

A general good practice is to handle every exception that is thrown, even if all you can do with it is log its occurrence and move on (which should be a last resort). If you have caught an exception at a place that you are powerless to handle it, perhaps you have caught the exception too early or too late. It is also a good idea that during development, you ensure that exceptions cause your program to fail, and to fail noisily. Furthermore, during production, be sure that exceptions cause your program to fail, and to fail gracefully. By ensuring that exceptions are handled during development, there will be fewer exceptions during production. There is certainly more tolerance for errors during development, than there is in production.

Throw Early, Catch Late

The idea behind the phrase “throw early, catch late” is that the earlier you throw an exception, the easier it is for you to determine what is going wrong and where; whereas the later you catch an exception, the more power you have to handle it correctly. This isn't talking about time, but rather, depth in the call stack.

I like to expand on the concept to say, “Throw as early, and catch as late as is reasonable”. It will become clear to you that while it isn't as easy to throw an exception too early, you can easily catch it too late, breaking cohesion and encapsulation, at the same time leaving you powerless to handle it correctly.

Central Error Processing

If your shop is using Continuous Integration, or Continuous Delivery with strong Test Driven Development techniques (RAD, XP, etc.), unit tests and feature toggling are very powerful tools in flushing out exceptional exceptions.  The concept of "central error processing" isn't an alternative to unit testing and CI/CD, but instead is a technique for further simplifying how exceptions are handled in your application.  I'll cover using unit tests and automated builds in a later section.

The concept of "central error processing" is to create a class, or set of classes that are responsible for handling and processing exceptions, most generally for reporting reasons.  A factory pattern is a great pattern to use in implementing said handling.

Central error processing improves encapsulation, at the cost of tighter coupling.  If a factory pattern is leveraged, this coupling can be minimized, also allowing for exceptions to be handled differently in different contexts.

If you feel that central exception processing is not acceptable for your exceptions, it is a good sign that you should check your exceptions to determine if they are truly exceptional.

Use "Finally" Blocks Appropriately

Finally blocks are provided to ensure that code that needs to be executed, regardless of exception is executed; the code in finally block will be executed whether or not an exception is thrown within the try block.  However, it is not appropriate to house code that should be run only under normal circumstances in a final block.

So what is the appropriate use of a finally block? Take a database connection into consideration: Databases manage pools of connections to their clients. These pools are limited to a set number of connections. In turn, the database expects clients to close the connection when it is finished. If a connection is not closed by the client, it will eventually timeout and be closed by the database. If an application acquires many connections quickly, without returning them to the pool, the system will eventually run out of connections, causing your application to fail.

With this understanding, it is clear that your application code should close a database connection, regardless of exception. This kind of housekeeping code belongs within the final block. Below is an example of closing a database connection in a finally block, written in C#:

Any code that requires cleanup or housekeeping, such as closing a database connection, that is at risk of exceptions, should be wrapped in a try/finally block, with the required cleanup code within the finally block.  In C#, the IDisposable pattern can be implemented to automatically handle closing resources, where the "Dispose" function is called regardless of exception.  In all reality however, this is simply a fancy way of implementing a try/finally construct.

Battling Exceptions: Unit Tests and Continuous Integration

The practice of unit testing is an excellent discipline for improving code quality.  Using unit tests bring so many benefits to a project, that a whole ideology has been built around the concept of writing strong unit tests for every portion of your code.  Many software professionals, myself included, strongly agree with the ideas and concepts developed by Kent Beck in test-driven development.

Using unit tests pragmatically yields numerous benefits, chiefly improving developer confidence and code quality, resulting in the project completing faster and more cleanly than other projects which don't leverage good unit testing practices.  Well written unit tests will quickly, and dependably flush out many exceptions; but it is wise to know that just because bugs are found via the unit tests, that there aren't more bugs in the system.

The concept of continuous integration was first introduced by Grady Booch when the "Booch Method" was published in 1993.  At it's simplest definition, continuous integration aims to compile, test (using unit tests), and integrate software often; daily, or even multiple times a day.  By automating this process and integrating it with a source control server, software engineers can get feedback from the build system of failed builds and unit tests upon commit to the server.  It is widely known that the sooner a problem is discovered and fixed, the cheaper and quicker it is;  which concept applies from farming, to plumbing to software.

It is important to remember that unit tests and continuous integration do not replace good exception handling practice, but instead, they are supplemental.  If good exception handling techniques help simplify and decrease the severity of exceptions, while unit tests and continuous integration help identify exceptions earlier and help guard against software decay.

Part II Conclusion

In part I, we learned what exceptional exceptions are.  Here in part II, we walked through a set of best-practices in respect of properly handling exceptional exceptions that would serve as a strong foundation to a policy on handling exceptions.

In the next, and final part of the article, we will take a look at "Unexceptional Exceptions", where I'll provide practical advice on how to better implement "Unexceptional Exceptions", without using the exception constructs of the language.

Part 3: Unexceptional Exceptions >

To view or add a comment, sign in

More articles by Ivan Pointer

  • What is a Programming Language?

    A Perspective from a Master Software Craftsman I sometimes hear from my friends and family: “how many programming…

    1 Comment
  • Complexity is like Fire

    Fire: beautiful, powerful, destructive. Fire nearly leveled a city, built over many years, in a mere six days.

    1 Comment
  • What I'm Reading

    I Like to Read Ask any of my colleagues, and they’ll confirm that I have an insatiable hunger for books. More…

  • Exceptional Exceptions Demystified - Part 3

    < Part 2: Handling Exceptional Exceptions Part III: Unexceptional Exceptions In Part 1, we took a look at what…

  • Exceptional Exceptions, Demystified - Part 1

    Introduction This article walks readers through a crash-course on what exceptions are in software engineering, and how…

    1 Comment

Explore content categories