Exceptions – How to Avoid Throwing Vexing Exceptions

exceptions

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.

Best Answer

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.

Related Topic