What Exactly Is An Exceptional Circumstance, Anyway?

I think that there's a general consensus out there that Exceptions should be limited to exceptional circumstances. But being that "exceptional" is a rather subjective adjective, there's a bit of a gray area as what is and isn't the appropriate use of Exceptions.

Let's start with an inappropriate use that we can all agree too. And I can think of no better place to find such an example than TheDailyWTF.com. Although that particular block of code doesn't exactly deal with Throwing exceptions, it is a very bad way of handling exceptions.

To the other extreme, exceptions are very appropriate for handling environment failure. For example, if your database throws "TABLE NOT FOUND," that would be the time to catch, repackage, and throw an exception.

But it's in the middle where there's a bit of disagreement. One area in particular I'd like to address in this post is exceptions to business rules. I mentioned this as an appropriate before, but noticed there was quite a bit of disagreement with that. But the fact of the matter is, exceptions really are the best way to deal with business rule exceptions. Here's why.

Let's consider a very simple business rule: an auction bid may be placed if and only if (a) the bid amount is higher than the current bid, (b) the auction has started, and (c) the auction has not ended. Because these rules (especially b and c) are domain constraints (i.e. they restrict the range of acceptable values), they are best handled by the Preserver of Data Integrity (some call this the "database"). To accomplish this validation, we'll use a stored procedure with an interface like this: procedure Place_Bid ( @Auction_Num char(12), @Bidder_Id int, @Bid_Amt money )

Now let's consider the layers of abstraction it actually takes to go from the user clicking the "Place Bid" button to the stored procedure being called:
•PlaceBidButton_Click()
•AuctionAgent.PlaceBid()
•IAuction.PlaceBid()
•--- physical tier boundary --
•IAuction.PlaceBid()
•Auction.PlaceBid()
•AuctionDataAgent.PlaceBid()
•SqlHelper.ExecuteCommand()
•--- physical tier boundary --
•procedure Place_Bid

Without using exceptions, it gets pretty ugly passing the message "Bid cannot be placed after auction has closed" from the stored procedure all the way back to the web page. Here's two popular ways of doing this:

  • Return Codes - Have every method that could potentially fail return some sort of value. True/False is the most common but rarely provides enough information about the failure. Our PlaceBid function would need four different return codes: Success, Fail-LowBid, Fail-EarlyBid, Fail-LateBid. Of course, this technique fails when your method may need to actually return something other than the return code.
  • Class Level Checking - For each of classes, add property called "LastError." This will contain an Error object that contains information about the last error (if one occurred). Simply check it after each operation.
  • Output Params - Add an out paramter to every method to pass back an ErrorObject. This is similar to the aforementioned technique except it is on the method-level.

In all three cases, you need to manually "bubble" up the message from method to method. As you can imagine, this adds lots and lots of needless "plumbing" code intertwined with your business logic. Since it's at the method level, all it takes is one developer to not code a method to return the right code.

The proper way of handling the Bid exception is, naturally, with Exceptions. When you raise the error in the stored procedure code, indicate that the message is a business rule exception intended to be displayed back to the end user. After that, you only need to put try/catch blocks in two places, the ExecuteCommand() method and the PlaceBidButton_Click() method.

ExecuteCommand() Psedo-code
Try
  sqlCmd.ExecuteNonQuery();
Catch ex As SqlExecption
  If IsUserSqlException(ex) Then Throw ConvertSqlExceptionToBusinessException(ex)
End Try

PlaceBitButton_Click() psedo-code
Try
  AuctionAgent.PlaceBid()
Catch ex As BusinessException
  DisplayUserMessageFromException(ex)
End Try

Less code, less mess. Nanoseconds slower? Probably. A big deal in the context of a web request going through three physical tiers? Not at all.

Published Friday, April 01, 2005 5:33 PM by Alex Papadimoulis

Comments

Friday, April 01, 2005 9:20 PM by Chris Martin

# re: What Exactly Is An Exceptional Circumstance, Anyway?

I respectfully disagree with your conclusion. If you're following any decent OOP practices, you will have an instantiated Auction object in your business layer that you can validate before you pass it to your persistance layer.

Exceptions *are* for "exceptional" situations where you really, as a developer, have almost no control over. One example that pops into mind is a WebException when a server cannot be found. How exactly are you going to know that when calling myWebRequest.GetResponseStream()?

In your example, all of your business rules can be checked before an exception be thrown. If you allow your exception to be thrown from the persistance layer, you are wasting time man! ;) Just define a new exception and throw it from your validator.

I know and understand your point, but I don't think it's a very good argument. Sorry that this was short and vague. I don't much time right now.

Chris
Saturday, April 02, 2005 1:11 AM by Alex Papadimoulis

# re: What Exactly Is An Exceptional Circumstance, Anyway?

I'm not sure I understand.

Are you suggesting that we don't enforce data integrity rules in the database?

Or should we duplicate our business logic in both the database and the business layer?
Saturday, April 02, 2005 12:37 PM by AndrewSeven

# re: What Exactly Is An Exceptional Circumstance, Anyway?

There is nothing exceptional about people entering data in the UI that will not meet integrity requirements ;)

If you don't find yourself saying "that should never happen" it might not be excpetional.

I'm confronted with this pattern at work.

AnonUser user= new AnonUser(userID,create);
If the userID exists and create==true, there is an exception thrown.
If userID doesn't exist and create==false, it throws an exception.
You must assume the (non)existence or catch the exception and re-try :(
I have yet to find any code that does anything else but create it if it in the catch if it didn't exist.

Using a factory method, and returning null would be much clearer.
AnonUser user= AnonUser.Find(userID);
AnonUser user= AnonUser.GetAssuredUser(userID);
Saturday, April 02, 2005 12:38 PM by AndrewSeven

# re: What Exactly Is An Exceptional Circumstance, Anyway?

create it in the catch if it didn't exist
Saturday, April 02, 2005 1:18 PM by BradC

# re: What Exactly Is An Exceptional Circumstance, Anyway?

I'm no OOP master, but I think I see the difference between these two strategies (correct me if I'm wrong):

Alex is taking the Bid request and submitting it all the way down to the database layer, which attempts to place the bid, but verifies that it is valid to place the bid (based on the business rules). The database layer is the one that returns an exception if it fails one of these checks.

Chris is advocating verifying these business rules in the business layer.

Here's the issue, as I see it: Chris's technique requires a double-trip to the database.
First: to get the "current price and status" of the auction. (to compare and validate)
Second: to actually submit the new bid.

This means that unless special care is taken, there is a chance that someone ELSE could submit a bid between the first and second calls, which makes the subsequent bid placement invalid. The bottom line is: Chris's bid request (second trip) is STILL going to have to do error checking at the db layer to verify that nobody else has placed a bid or the auction hasn't closed in the meantime.

There are ways to do this, I suppose. The "get auction info" call could put a flag on the data indicating a claim on next bid, so if other requests are made, they are rejected.

I find it extremely helpful to discuss this in the context of specific examples, instead of the abstract level. After all, each situation is different, and may require different techniques.
Saturday, April 02, 2005 5:15 PM by Tim Haines

# re: What Exactly Is An Exceptional Circumstance, Anyway?

I'd have to back Chris up and say rules like this should be checked in the business layer.

BradC has raised a point saying that you need to be cautious that other bids aren't received between the time the validation is done and the actual insert - but this is pretty similar to any other concurrency problem.
Tuesday, April 05, 2005 12:43 PM by TrackBack

# Exception Throwing Design Guidelines : Performance (another religious war?)

Wednesday, April 13, 2005 10:18 PM by Luis

# You're all wrong

Or, alternatively, you're all right, but none of you is right enough. In the ideal case, the business logic should be enforced in the database as constraints/triggers, *and* the app should validate data before sending it to the database. The key is to achieve this without duplicating business logic.

This means that you need an implementation-independent representation of the constraints, which can be translated into database constraints by your schema management tool, and object validation rules by your O/R mapper.

I've not seen such a system, but my company has something a bit similar: a form field validation language that gets translated into two things: (a) rules for validating the form object before persisting it to the database; and (b) dynamically generated Javascript code to do the same validation on the client's web browser. In principle, we could add (c) translating the rules into database constraints, but that's really, really hard to do in a database-independent manner.

The key here is that each layer of validation is doing different things: the database server is guaranteeing data integrity in the face of non-conformant clients; the app-server validation is saving us a trip to the database, plus it can deal with validation failures in an application-specific way (e.g. displaying a response page that highlights the error fields in red); the client-side Javascript validation is providing the user a faster, more interactive UI (and reducing some load on the app servers).
Friday, July 14, 2006 5:32 PM by Mike Gates

# re: What Exactly Is An Exceptional Circumstance, Anyway?

I know I'm jumping in here a bit late but I thought I might add my opinion for the record :)

An exception should be thrown from any abstraction layer when the requested behavior of that abstraction layer could not be completed. For example, Calling FileSystem.OpenFile should throw a FileCouldNotBeFoundException if the requested file could not be found. In any given app, it could be a regular occurance (not exceptional) that a requested file is not found. For example, go to the File->Open in notepad and enter in a non-existing filename. Deep in the code for notepad, there could be a call to FileSystem.OpenFile, which in this case would throw a FileCouldNotBeFoundException. This would be caught in the appropriate abstraction layer (could be the GUI layer), where the error box gets sent to the user. It is also possible that there is some call to FileSystem.DoesFileExist(...). This should not throw an exception if the file could not be found because the DoesFileExist logic could be completed fine, and returns its value of false, no need for an exception.

So, my point is, you can't decide whether or not to throw an exception based on whether the situation is "exceptional" in the application. You should throw an exception if the requested logic could not be performed for any reason. At least, this is my guideline.

# Curious Coding » Quick Tip: Finding hidden exceptions in your .NET application

Pingback from  Curious Coding » Quick Tip: Finding hidden exceptions in your .NET application

Leave a Comment

(required) 
(required) 
(optional)
(required)