Tuesday, 6 December 2011
Exceptions for Exceptional Things Only!
Exceptions are a strange beast. They are actually worded correctly and are supposed to relate to things that shouldn't happen such as malformed values being parsed and calling services with incorrect data. However, the line has been blurred between what is genuinely exceptional and what is merely incorrect. For example, if you have a service that is called by a web application and a certain method on that service takes a string which represents an integer, it would be reasonable to throw an exception if the string is null or not parsable as an integer. It would be part of the contract for that method (and could be enforced other ways as it happens) but represents a genuine "exception", a problem in application logic or design. Consider a similar example where a user inputs an integer into a text field on a web app and submits it to the code behind. In this case, an exception should NOT be used since this is not exceptional at all but completely expected (people type things in wrongly all the time). Why not use exceptions in this case? Well they seem pretty useful so I guess we could but there are two reasons why we shouldn't. Firstly it confuses our minds as to the correct use of exceptions, we start blurring the line between logic/business/coding errors and simple error handling. The second reason is performance. When an exception is thrown, the framework has to load up a debugging engine which loads the call stack data and which takes a significant amount of resource. This resource is closed once the exception is handled and then opened again if another exception occurs. Why? Because it is designed to be for exceptional conditions and not for normal logic flow. As an example, I ran a test app to compare Int.Parse with Int.TryParse while parsing a bad string. I caught the exception from Parse locally and ignored it. It took 4.24 seconds to run 100,000 exceptions and only 2mS to run 100,000 Tries (2000 times slower to allow exceptions to be part of normal logic). That last line gives a clue to how we might avoid exceptions. We can use alternative methods and operations. It should only be at boundaries that we need to check formats (user interface and interfaces with other systems) anything internally to our application should behave. If we are therefore testing text fields for correctness then we can either use the TryParse family of methods or use a RegEx to specify exactly what we want something to look like. That way we can call IsMatch as a boolean check rather than allow a parsing operation to throw. We can similarly check other fields and types using simple if ( ... ) return false or if ( ... ) DisplayError() bearing in mind that our checks are only useful if we can inform the user or a system admin that something has been entered correctly. As an acid test, you should generally never see an exception caught and ignored. Catching, logging and then continuing might be valid (as long as you have a way of fixing the underlying problem) but if you ignore the exception, you have made it unexceptional.