[Back to articles] What The Hell Is Exception?The First SightStart With The Specification Robustness Vs Correctness Why To Throw Exception? Why However Not To Return Error Code? Exceptions In Enterprise Applications The Error-Handling Policy Best Practices Sources The First SightIt was 2003 when I had faced the mention of exceptions for the first time. Actually, it didn’t appear to me at the time that using exceptions could do some good for me, and at the beginning I used them just occasionally, without thinking too much about what they are and what they are for. In fact, what had been changed for me when my programming language had became to provide a support for exception handling? It’s was pretty clear to me that exceptions are the way to deal with erroneous situations. But having several years of experience of programming on C, I used to utilize the common approach that consisted in the following: to return a special code as a result of each method, 0 for good and -1 for bad, like this:
If I needed more precise diagnostics, I returned different codes having different meanings. There was a one little problem anyway: when I was too lazy I just ignored all the return codes hoping that the bad things will never happen:
Shame on me... But in the C#, the language that does support exceptions, it isn’t considered anymore as a bad form. Quite the contrary, for this language this is the absolutely normal thing! In the C# you as a rule write a method that doesn’t return any value at all:
And you call it just the same way:
What’s wrong with the C#? Does it encourage the careless way of programming? Actually, the question needs some investigation. Let’s begin. Start With The SpecificationWhy to talk about exceptional situations at all? After all, at the moment that you begin to write any code you usually know what do you want to implement. Of course, any algorithm, except the trivial one, generally has at least two flows: the basic one and the alternative one. In such a case, the basic flow achieves an object and the alternative one processes any invalid condition. Look at the figure below: ![]() Figure 1. The order processing flow chart. This is an algorithm of the order processing. When the system receives an order, it checks whether the credit card is valid and whether the product is in stock. In the case both conditions are met it processes the credit card and delivers the product. If any condition cannot be met, the order is being cancelled. It isn’t a bit exceptional for the program that implements this algorithm that the credit card can be invalid. Moreover, the good specification considers all the alternative scenarios; it must expect wrong conditions and tell how to deal with them. The code should be consistent with the specification to be correct [1]. What are that exceptions that we are talking about? By this moment we haven’t found a place for them yet. Robustness Vs CorrectnessLet’s take a bit more detailed look on the order processing unit specification. Consider two units: "Check Credit Card" and "Process Credit Card". Actually, it is the "Check Credit Card" unit that expects the credit card to be invalid, the "Process Credit Card" unit doesn’t. If the credit card will be invalid when the "Check Credit Card" unit will be executed, this unit will just return the "Invalid" status. The unit client for its turn expects this result and knows what to do further. But could you say what will be if the credit card will be invalid at the moment of the "Process Credit Card" unit execution? Looking at this specification, I can’t. This is quite abnormal situation of course, but the good system should react appropriately at situations like this. Such ability is called "Robustness". Robustness always complements correctness [1]. How could we describe this abnormal situation? This is the exception, actually.
This definition is quite rough but right. The figure below illustrates it: ![]() Figure 1. Modules expectations. In fact, for the "Process Credit Card" unit the credit card invalidity will not be only the bolt from the blue, this will be an obstacle that will disallow the unit to perform its task. It’s not the single obstacle that could be encountered. For example, the connection to the bank billing system can be broken. There could be internal error. Anyway, the unit client expects that on the "Process Credit Card" ending the credit card will be processed but it will not. That’s why the unit cannot simply finish its work with any return code - it will be inconsistent with its client expectations. We can express it more precisely in the terms of pre- and post-conditions. "Check Credit Card" unit
"Process Credit Card" unit In such a way, the abnormality of examined situation consists in the unit pre- or post- conditions violation, disallowing it to fulfill its task.
This definition is much more precise.
Why To Throw Exception?Really, why don’t handle the abnormal situation locally, just at the place it had happened? Well, sometimes it is a good option. Actually, when any error occurs we could do the following [2]:
Let’s consider the following code:
Suppose the index variable value is outside of the array boundary. What could be the rational decision when you encounter such situation? Display an error message to the user? Well, and what next? What should the user do? Return an error code? Well, what code could it be? How could the method client distinguish it from the meaningful value? The only reasonable answer will be "To terminate the method execution immediately", that in fact means "To throw an exception". Why However Not To Return Error Code?Lets come back to the given code example. It shows that in the case the method in intended to return a meaningful value it cannot return an error code instead because the calling code could not determine whether the method result is a meaningful value or an error code. I insisted that in the given scenario to throw an exception will be the only suitable decision. But what if we rewrite the code in that way that the method will always return an error code as a result, and the meaningful value – as an output parameter? The following code demonstrates this approach:
This is in fact the same approach I used rather often programming on C. Why is this approach worse than exception-based one? Actually, these are many reasons to prefer exceptions to error return codes [4]:
And one of the most important advantages of using exceptions is that exception can’t easily be ignored [4]. I was cunning a bit when I said in the beginning that in the C# you can call a method ignoring any error, like the following:
Actually, if the exception will be thrown in the DoSomething() method, the code execution will be interrupted and exception message box will be shown to the user even if the exception won’t be caught in the catch section higher in the call stack. Exceptions In Enterprise ApplicationsWell, we have discovered that throwing an exception in the situation the method cannot fulfill its task gives us plenty of advantages, but what to do then, after you catch an exception? In reality, that greatly depends on the place where the exception had been thrown and where it had been caught. At least four ways of exception handling could be distinguished. Look at the figure below: ![]() Figure 1. Exception handling alternatives. This is the rather common enterprise application layered architecture: the data access layer, the business logic layer and the representation (UI) layer. Depending on layer the exception is being thrown on, it can be handled in three different ways. First, the exception could be wrapped. Second, it could be propagated. Third, it could be replaced [4]. Let’s examine these alternatives more closely. Propagating an ExceptionIn this scenario you catch an exception, perform some actions that you need, and re-throw an original exception unchanged. Thus you give the upper layer an ability to handle the exception on the higher level of abstraction, and at the same time you allow the current layer to implement some actions needed (to log an exception, for instance). The catch block will be similar to the following:
Wrapping an ExceptionIn this scenario you wrap an exception within another one. Doing so you save layer above from low-level specific details, but at the same time you preserve all these details for the further analysis if this will be needed. The catch block will be similar to the following:
Replacing an ExceptionIn this scenario you catch the original exception and throw the new one. This could be useful if you want to let the layer above handle the situation but do not want it to be aware of any sensitive, secure or unnecessary information. The catch block will be similar to the following:
To Undertake Some Custom ActionsThis is still being an option, of course. It differs slightly from the case in what you process the situation right on the place it occurs, without throwing any exception at all. The difference is that in this case you can put the custom code in special place, separated from the business logic code. Moreover, you handle an exception on a little bit higher level of call stack that means ‘on the higher level of abstraction’. This gives you an ability to choose more appropriate decision based on current system state and other conditions. The Error-Handling PolicyAs we have considered just now, when we catch an exception we have several possibilities giving us different results. What to prefer? There is no the simple answer. Anyway, you should consider the following:
The best way to get ready to answer all these questions is to create an error-handling policy for the whole application. This policy can look like the following: UI LayerGoal: To inform user about errorsAction: Write error log Action: Show exceptions details to the user Goal: To prevent data loss Action: Handle all known error situations (cannot write to the file, cannot access server) Goal: Not to allow user to do something wrong Action: Disable controls Action: Validate user input Controller LayerGoal: To deliver UI layer from low-level error detailsAction: Handle some unexpected situations Action: Wrap low-level exceptions with high-level exceptions ("Cannot access the database" instead of "IP package #234 lost") Goal: To inform UI layer about any errors Action: Wrap low-level exceptions with high-level exceptions ("Cannot access the database" instead of "IP package #234 lost") Goal: Not to allow user to do something wrong Action: Validate input data Action: Correct input data automatically Domain LayerGoal: Not to allow data to be inconsistentAction: Validate input data Action: Throw exceptions in the case the operation could not be completed in the consistent state Action: Put all the details into exception information This policy describes a common strategy for error prevention and handing. You can put more specific information here. Best PracticesI would like to finish this article by little review of rules and best practices regarding programming with exceptions I have collected from different sources during last several years. Here they are, basically that of them that seem to me to be the most important: Do not swallow an exception!This is the first and main rule. Never ever handle exceptions this way:
Throw an exception only for conditions that are truly exceptional [2]Exception is not the way to pass the data to the calling code and not the mean to control the flow of program execution. It gives us just an ability to interrupt the unit execution immediately and to get the calling code informed about the cause of this interruption. Thus, using the exception like the "EndOfListException" will not be correct. You know that advancing along the list you will reach the end of it at last; it’s not the exceptional situation but the expected one. It's quite another matter with the stack, because before you pull something out of it you should put it there first. When you trying to pull the value from the stack you are sure it has one, otherwise it becomes a really unexpected situation. It will be a good choice to throw the "StackIsEmptyException" in this situation. Consider an exception as the violation of a programmatic interface’s implicit assumptions [4]When you define a class interface you usually think about in and out parameters and the method main goal. But you almost never ask yourself about things like these: is there will enough memory to create objects? Is there any chance that you will encounter an error in the framework? What if the card will become invalid at the moment you want to charge it, after the check had been already performed? Actually you doesn’t need to ask, because if thing like this will occur you will get an exception and that’s all. You don’t need to think about all the things like these. The exception that could be thrown is the only thing you should be prepared for. Define who is responsible for handling an exceptionWhen you throw an exception you should always be sure you know who is responsible to handle it. When you call a method that can potentially throw an exception you should decide whether it is your responsibility to catch and handle this exception. If it isn’t – don’t catch the exception. Throw exceptions at the right level of abstraction [2]Remember that exception that class can throw is the part of the class interface along with its methods. The same way as you design methods to be relevant to the class, design exceptions the class throws to be relevant to this class. Exception handling policy should be different for the library and the application [4]When you develop a library is should not be your goal to make any policy decisions regarding exception handling. The calling code should decide what to do with the exception. But when you develop an application you should define an exception handling policy. Throw an ArgumentException or a class derived from ArgumentException if invalid parameters are passed [5]It’s better to demonstrate this in code:
Always order exceptions in catch blocks from the most specific to the least specific [5]This technique handles the specific exception before it is passed to a more general catch block. Here is an example:
Always throw the most specific exception [4]For example, throw the ArgumentNullException or ArgumentOutOfRangeException instead of ArgumentException whenever it is possible. Sources
© Artem Kondratyev 2007
|
||||||||||||||||||||||||||||