Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Reading Eric Lippert's article on exceptions was definitely an eye opener on how I should approach exceptions, both as the producer and as the consumer. However, I'm still struggling to define a guideline regarding how to avoid throwing vexing exceptions.

Specifically:

  • Suppose you have a Save method that can fail because a) Somebody else modified the record before you, or b) The value you're trying to create already exists. These conditions are to be expected and not exceptional, so instead of throwing an exception you decide to create a Try version of your method, TrySave, which returns a boolean indicating if the save succeeded. But if it fails, how will the consumer know what was the problem? Or would it be best to return an enum indicating the result, kind of Ok/RecordAlreadyModified/ValueAlreadyExists? With integer.TryParse this problem doesn't exist, since there's only one reason the method can fail.
  • Is the previous example really a vexing situation? Or would throwing an exception in this case be the preferred way? I know that's how it's done in most libraries and frameworks, including the Entity framework.
  • How do you decide when to create a Try version of your method vs. providing some way to test beforehand if the method will work or not? I'm currently following these guidelines:
    • If there is the chance of a race condition, then create a Try version. This prevents the need for the consumer to catch an exogenous exception. For example, in the Save method described before.
    • If the method to test the condition pretty much would do all that the original method does, then create a Try version. For example, integer.TryParse().
    • In any other case, create a method to test the condition.
share|improve this question
1  
Your example of a save that may fail isn't really a terribly vexing exception. It's quite ordinary and probably should simply be an exception. –  S.Lott Feb 6 '12 at 15:46
    
@S.Lott: What do you mean with it's quite ordinary? The situation itself, or throwing an exception in this situation? Anyway, I agree with you that it's not evident if this is in fact a vexing situation. I'll update the question. –  Mike Feb 6 '12 at 16:21
    
"The situation itself, or throwing an exception in this situation" Both. –  S.Lott Feb 6 '12 at 16:31
add comment

3 Answers 3

up vote 16 down vote accepted

Suppose you have a Save method that can fail because a) Somebody else modified the record before you, or b) The value you're trying to create already exists. These conditions are to be expected and not exceptional, so instead of throwing an exception you decide to create a Try version of your method, TrySave, which returns a boolean indicating if the save succeeded. But if it fails, how will the consumer know what was the problem?

Good question.

The first question that comes to my mind is: if the data is already there then in what sense did the save fail? It sure sounds like it succeeded to me. But let's assume for the sake of argument that you really do have many different reasons why an operation can fail.

The second question that comes to my mind is: is the information you wish to return to the user actionable? That is, are they going to make some decision based on that information?

When the "check engine" light comes on, I open up the hood, verify that there is an engine in my car that is not on fire, and take it to the garage. Of course at the garage they have all kinds of special purpose diagnostic equipment that tells them why the check engine light is on, but from my perspective, the warning system is well designed. I do not care whether the problem is because the oxygen sensor is recording an abnormal level of oxygen in the combustion chamber, or because the idle speed detector is unplugged, or whatever. I'm going to take the same action, namely, let someone else figure this out.

Does the caller care why the save failed? Are they going to do anything about it, other than either give up or try again?

Let's assume for the sake of argument that the caller really is going to take different actions depending on the reason why the operation failed.

The third question that comes to mind is: is the failure mode exceptional? I think you might be confusing possible with unexceptional. I would think of two users attempting to modify the same record at the same time as an exceptional-but-possible situation, not a common situation.

Let's assume for the sake of argument that it is unexceptional.

The fourth question that comes to mind is: is there a way to reliably detect the bad situation ahead of time?

If the bad situation is in my "exogenous" bucket, then, no. There's no way to reliably say "did another user modify this record?" because they might modify it after you ask the question. The answer is stale as soon as it is produced.

The fifth question that comes to mind is: is there a way to design the API so that the bad situation can be prevented?

For example, you could make the "save" operation require two steps. Step one: acquire a lock on the record being modified. That operation either succeeds or fails and so can return a Boolean. The caller can then have a policy about how to deal with failure: wait a while and try again, give up, whatever. Step two: once the lock is acquired, do the save and release the lock. Now the save always succeeds and so there is no need to worry about any kind of error handling. If the save fails, that is truly exceptional.

share|improve this answer
    
All very good points, thanks. Now here's a rhetorical question that probably summarizes my post: If you were to redesign File.Open(), would you create a File.TryOpen() instead? How would you communicate the consumer the reason of the fail? Or is throwing an exogenous exception really the best compromise here? –  Mike Feb 6 '12 at 17:02
5  
@Mike: File systems are a good example of the use of exogenous exceptions. They fail rarely, so failure is exceptional. They fail unpredictably and for reasons entirely outside of the control of the caller (there is no "lock" you can take that keeps the ethernet cable plugged in), and the failures are both diverse and actionable (that is, a failure because file is not found vs file is found but you don't have write access are both actionable in different ways.) All these are reasons to represent a failure as an exception. –  Eric Lippert Feb 6 '12 at 17:21
    
I do consider the question to be answered now, but I'm just curious ;) If the method can fail for 2 or more reasons, the failures are actionable, the failures are unexceptional and the failures cannot be detected ahead of time nor prevented, what would you do? –  Mike Feb 6 '12 at 19:50
    
@Mike I can't speak for Eric, but that sounds like a good place for error codes. Returning a member of an enum, perhaps. –  Matthew Read Jun 2 '12 at 20:26
add comment

In your example, if the ValueAlreadyExists situation can be easily checked, it should be checked and an exception could be raised before attempting the Save, I don't think a Try should be necessary in this situation. The race condition is harder to check ahead of time, so wrapping the Save in a Try in this case is probably a very good idea.

In general, if there's a condition that I think is very likely (such as NoDataReturned, DivideByZero, etc...) OR is very easy to check for (such as an empty collection or a NULL-value), I try to check for it ahead of time and deal with it before I ever get to the point where I'd have to catch an exception. I admit it's not always easy to know these conditions ahead of time, sometimes they only appear when the code is under rigourous testing.

share|improve this answer
add comment

The save() method must throw an exception.

The top-most layer should catch and inform the user, without terminating the program, unless it's an Unix like command line programs, in which case it's OK to terminate.

Return values are not a good way to manage exceptions.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.