When your program fails, you want to throw the right type of exception. You want to provide useful information in the type so the caller can react appropriately to the problem. Lack of type information is one reason why throwing an exception of type Exception is such a bad idea. The .net base class library includes exceptions for most common cases (ArgumentException, InvalidOperationException, IOException, etc.). This post covers a class of custom exceptions I like to use in my own projects, which I call invalid state exceptions.
Description
Most bugs immediately cause an exception to be thrown: if you try to pass null into a non-null method you get an ArgumentNullException, and if you try to pop an empty stack and you get an InvalidOperationException. But the difficult bugs don't throw exceptions immediately: they occur, corrupt the program state, and at some later point this causes an exception to be thrown.
Enter the InvalidStateException. An invalid state exception occurs when the program fails because a state-corrupting bug has already occurred. For example, a list enumeration failing because the list was modified should be an InvalidStateException instead of an InvalidOperationException.
'''<summary>Indicates an invalid program state has been detected (eg. due to a fault).</summary>
Public Class InvalidStateException
Inherits InvalidOperationException
Public Sub New(Optional ByVal message As String = Nothing,
Optional ByVal innerException As Exception = Nothing)
MyBase.New(If(message, "Reached an unexpected state."), innerException)
End Sub
End Class
Invalid state exceptions are generally irrecoverable because it is already too late to fix the problem. They tend to indicate bugs in the interaction of multiple components, and are usually thrown by assumption-checking code. I have found that adding this exception type has increased the amount of assumption checking code I write, which is definitely a good thing.
Derived Types
Usually we know more than just "the program is in an invalid state" when throwing an invalid state exception. The most common assumption I check is that a switch statement hits a defined case, so I have a derived type called ImpossibleValueException. Here are the three derived types I have used:
1. UnreachableException: I usually use this one to silence the compiler's reachability analyzer or to indicate to readers that a case is impossible.
'''<summary>Indicates a path expected to be unreachable has been executed.</summary>
Public Class UnreachableException
Inherits InvalidStateException
Public Sub New(Optional ByVal message As String = Nothing,
Optional ByVal innerException As Exception = Nothing)
MyBase.New(If(message, "Reached a state which was expected to not be reachable."), innerException)
End Sub
End Class
2. ImpossibleValueException: I often throw this one in the default cases of switch statements over enums. I even defined an extension method for constructing it from any value.
'''<summary>Indicates an internal value expected to be impossible has been encountered.</summary>
Public Class ImpossibleValueException(Of T)
Inherits InvalidStateException
Public ReadOnly Value As T
Public Sub New(ByVal value As T,
Optional ByVal message As String = Nothing,
Optional ByVal innerException As Exception = Nothing)
MyBase.new(If(message, "The {0} value ""{1}"" was not expected.".Frmt(GetType(T).Name, String.Concat(value))),
innerException)
Me.Value = value
End Sub
End Class
3. InfiniteLoopException: I defined this one when making a single linked list iterator with cycle detection.
'''<summary>Indicates the program entered an infinite loop but the problem was caught and the loop aborted.</summary>
Public Class InfiniteLoopException
Inherits InvalidStateException
Public Sub New(Optional ByVal message As String = Nothing,
Optional ByVal innerException As Exception = Nothing)
MyBase.New(If(message, "Detected and aborted an infinite loop."), innerException)
End Sub
End Class
Conclusion
The invalid state exception is not well covered by any of the default exception types, which is why I defined it. Once defined, I found myself using it in all kinds of (appropriate!) places. The moral: don't be afraid to define generic exceptions when you find yourself shoehorning a weird exception into the existing types. (Just don't go crazy.)
No comments:
Post a Comment