Sunday, November 2, 2008

Error Raising & Handling

The following rules outline the guidelines for raising and handling errors:
1. All code paths that result in an exception should provide a method to check for success without throwing an exception. For example, to avoid a FileNotFoundException you can call File.Exists. This might not always be possible, but the goal is that under normal execution no exceptions should be thrown.
2. Exceptions classes should always be serializable.
3. End Exception class names with the Exception suffix as in the following code example.
Visual Basic
_ Public Class FileNotFoundException
Inherits Exception
' Implementation code goes here.
End Class
C# [Serializable()]
public class FileNotFoundException : Exception
{
// Implementation code goes here.
}
4. Use the three common constructors shown in the following code example when creating exception classes in C# and the Managed Extensions for C++.
Visual Basic
Public Class XXXException
Inherits ApplicationException
Public Sub New()
' Implementation code goes here.
End Sub
Public Sub New(message As String)
My.Base.New(message)
End Sub
Public Sub New(message As String, inner As Exception)
My.Base.New(message, inner)
End Sub
End Class
C#
public class XXXException : ApplicationException
{
XxxException() {... }
XxxException(string message) {... }
XxxException(string message, Exception inner) {... }
}
5. In most cases, use the predefined exception types. Only define new exception types for programmatic scenarios, where you expect users of your class library to catch exceptions of this new type and perform a programmatic action based on the exception type itself. This is
in lieu of parsing the exception string, which would negatively impact performance and maintenance. For example, it makes sense to define a FileNotFoundException because the developer might decide to create the missing file. However, a FileIOException is not something that would typically be handled specifically in code.
6. Do not derive new exceptions directly from the base class Exception. Instead, derive from ApplicationException.
7. Group new exceptions derived from the base class Exception by namespace. For example, there will be derived classes for XML, IO, Collections, and so on. Each of these areas will have their own derived classes of exceptions as appropriate. Any exceptions that other library or application writers want to add will extend the Exception class directly. You should create a single name for all related exceptions, and extend all exceptions related to that application or library from that group.
8. Use a localized description string in every exception. When the user sees an error message, it will be derived from the description string of the exception that was thrown, and never from the exception class.
9. Create grammatically correct error messages with punctuation. Each sentence in the description string of an exception should end in a period. Code that generically displays an exception message to the user does not have to handle the case where a developer forgot the final period.
10. Provide exception properties for programmatic access. Include extra information (other than the description string) in an exception only when there is a programmatic scenario where that additional information is useful. You should rarely need to include additional information in an exception.
11. Do not use exceptions for normal or expected errors, or for normal flow of control. (Throwing an exception is an extremely costly operation.)
12. You should return null for extremely common error cases. For example, a File.Open command returns a null reference if the file is not found, but throws an exception if the file is locked.
13. Design classes so that in the normal course of use an exception will never be thrown. In the following code example, a FileStream class exposes another way of determining if the end of the file has been reached to avoid the exception that will be thrown if the developer reads past the end of the file.
Visual Basic
Class FileRead
Sub Open()
Dim stream As FileStream = _
File.Open("myfile.txt",FileMode.Open)
Dim b As Byte
' ReadByte returns -1 at end of file.
While b = stream.ReadByte() <> true
' Do something.
End While
End Sub
End Class

C#
class FileRead
{
void Open()
{
FileStream stream =
File.Open("myfile.txt", FileMode.Open);
byte b;
// ReadByte returns -1 at end of file.
while ((b = stream.ReadByte()) != true)
{
// Do something.
}
}
}
14. Throw the InvalidOperationException exception if a call to a property set accessor or method is not appropriate given the object's current state.
15. Throw an ArgumentException or create an exception derived from this class if invalid parameters are passed or detected.
16. Be aware that the stack trace starts at the point where an exception is thrown, not where it is created with the new operator. Consider this when deciding where to throw an exception.
17. Use the exception builder methods. It is common for a class to throw the same exception from different places in its implementation. To avoid repetitive code, use helper methods that create the exception using the new operator and return it. The following code example shows how to implement a helper method.
Visual Basic
Class File
Private mFileName As String
Public Function Read(bytes As Int32) As Byte()
If Not ReadFile(handle, bytes) Then
Throw New FileIOException()
End If
End Function
Private Function NewFileIOException() As FileException
Dim Descroption As String = _
' Build localized string, include fileName.
Return New FileException(Descroption)
End Sub
End Class
C#
class File
{
string mFileName;
public byte[] Read(int bytes)
{
if (!ReadFile(handle, bytes))
throw NewFileIOException();
}
FileException NewFileIOException()
{
string description =
// Build localized string, include fileName.
return new FileException(description);
}
}
18. Throw exceptions instead of returning an error code or HRESULT.
19. Throw the most specific exception possible.
20. Create meaningful message text for exceptions, targeted at the developer.
21. Set all fields on the exception you use.
22. Use Inner exceptions (chained exceptions). However, do not catch and re-throw exceptions unless you are adding additional information and/or changing the type of the exception.
23. Do not create methods that throw NullReferenceException or IndexOutOfRangeException. (These are hard debug when the application is in a production environment as they do not give enough information about the runtime situation.)
24. Perform argument checking on protected (Family) and internal (Assembly) members. Clearly state in the documentation if the protected method does not do argument checking. Unless otherwise stated, assume that argument checking is performed. There might, however, be performance gains in not performing argument checking.
25. Clean up any side effects when throwing an exception. Callers should be able to assume that there are no side effects when an exception is thrown from a function. For example, if a Hashtable.Insert method throws an exception, the caller can assume that the specified item was not added to the Hashtable.
26. Very rarely “absorb” exceptions by having a try…catch block that does nothing.
27. Consider using try…finally without a catch block as a “last chance” option to restore state. This is a powerful method of ensuring that locks are unlocked or that state is returned to it’s original values when an error occurs.
28. Consider that if you overload ToString on an exception class, ASP.NET does not use ToString to present the exception to the developer, hence any additional state information you dump in the ToString method will not be shown.
Standard Exception Types
The following table lists the standard exceptions provided by the runtime and the conditions for which you should create a derived class.

Exception type Base type Description Example

Exception Object Base class for None (use a derived class of this-
all exceptions. Exception)

SystemException Exception Base class for all runtime None
generated errors.

No comments: