| Two things developers never
seem to have time for are documentation
and error handling. Documentation -- well, C#, along
with the HTML
Help Workshop help compiler and free tools like NDoc
make creating MSDN-quality full text searchable CHM help files easy and
fun. Error handling -- that's another story. The biggest offender is the
empty catch block:
try
{
// Your
crappy buggy code here
}
catch (Exception)
{ // Duh, nada! }
// yippee! jes
keep right on truckin!
This is a poor excuse for error handling, its plain BAD programming practice,
and it can also cause PERFORMANCE PROBLEMS. I spent a whole bunch of time
up at the MS Testing lab in Charlotte, and all the gurus there told me
the same thing: Avoid having exceptions in the normal operation of your
.NET programs. And, they showed me AD Plus and Mutex Black Box traces
of why it was bad, too. Those reports were UGLY - especially when we saw
how many CPU cycles our crappy, exception-generating code was burning
up!
Use Exceptions Wisely
You should only throw exceptions when a condition outside of your code's
assumptions occurs. In other words, don't use exceptions as a way to provide
your intended functionality even if something bad happens.
For example, a user might enter an invalid user name or password while
logging onto an application. While this is not a successful logon, it
should be an expected result, and therefore it should not throw an exception.
Same with loading an XmlDocument, or reading from a file. In most (not
all) cases, it is possible to construct one's code so that it is never
necessary to have the ugly code shown above. Many failure conditions from
.NET methods return null. You don't need to have an empty catch block
to test for that. There are other methods such as double.TryParse() which
return true or false (as opposed to double.parse(), which throws an exception)
and with which it is possible to construct much more elegant and functional
code.
However, an exception should be generated if a real, legitimate unexpected
condition occurs, such as an unavailable user database. Throwing or allowing
exceptions to be thrown is more expensive than simply returning a result
to a caller or having code to handle an expected condition. Therefore
exceptions should not be used to control the normal flow of execution
through your code.
Excessive use of exceptions can create unreadable and unmanageable code,
and cause a nasty condition called stress headache to other developers
who must read through one's code.
Design your classes so that an exception is not thrown in normal use.
For example, a FileStream class exposes
a way of determining whether the end of the file has been reached. This
avoids the exception that is thrown if you read past the end of the file.
The following example shows how to read to the end of the file.
class FileRead
{
void Open() {
FileStream stream = File.Open("myfile.txt", FileMode.Open);
byte b;
// ReadByte
returns -1 at EOF.
while ((b == stream.ReadByte()) != true) {
// Do something. No Exceptions!
}
}
}
How the Runtime Manages Exceptions
An exception is any error condition or unexpected behavior encountered
by an executing program. Exceptions can be raised because of a fault in
your code or in code you call (such as a shared library), operating system
resources not being available, unexpected conditions the common language
runtime encounters (such as code that cannot be verified), and so on.
Your application can recover from some of these conditions, but not others.
While you can recover from most application exceptions, you cannot recover
from most runtime exceptions.
In the .NET Framework, an exception is an object that inherits from the
Exception Class class. An exception is thrown from an area of code where
a problem has occurred. The exception is passed up the stack until the
application handles it or the program terminates. For more information
on handling exceptions using the .NET Framework, see The Exception Class
in the .NET Documentation.
The runtime uses an exception handling model based on exception objects
and protected blocks of code. An Exception object is created to represent
an exception when it occurs.
The runtime creates an exception information table for each executable.
Each method of the executable has an associated array of exception handling
information (which can be empty) in the exception information table. Each
entry in the array describes a protected block of code, any exception
filters associated with that code, and any exception handlers (catch statements).
This exception table is extremely efficient and there is no performance
penalty in processor time or in memory use when an exception does not
occur. You use resources only when an exception occurs.
The exception information table represents four types of exception handlers
for protected blocks:
- A finally handler that executes whenever the block exits, whether
that occurs by normal control flow or by an unhandled exception.
- A fault handler that must execute if an exception occurs, but does
not execute on completion of normal control flow.
- A type-filtered handler that handles any exception of a specified
class or any of its derived classes.
A user-filtered handler that runs user-specified code to determine whether
the exception should be handled by the associated handler or should
be passed to the next protected block.
- Each language implements these exception handlers
according to its specifications. For example, Visual Basic .NET provides
access to the user-filtered handler through a variable comparison (using
the When keyword) in the catch statement; C# does not implement the
user-filtered handler.
When an exception occurs, the runtime begins a two-step process:
The runtime searches the array for the first protected block that:
Protects a region that includes the currently executing
instruction, and
Contains an exception handler or contains a filter that
handles the exception.
If a match occurs, the runtime creates an Exception object that describes
the exception. The runtime then executes all finally or fault statements
between the statement where the exception occurred and the statement handling
the exception. Note that the order of exception handlers is important:
the innermost exception handler is evaluated first. Also note that exception
handlers can access the local variables and local memory of the routine
that catches the exception, but any intermediate values at the time the
exception is thrown are lost.
If no match occurs in the current method, the runtime searches each caller
of the current method, and it continues this path all the way up the stack.
If no caller has a match, the runtime allows the debugger to access the
exception. If the debugger does not attach to the exception, the runtime
raises the UnhandledException event. If there are no listeners for the
UnhandledException event, the runtime dumps a stack trace and ends the
program.
In most of the .Net projects I've worked on, we've included special logging
and exception-handling classes that make the work of wiring up, logging,
and tracing errors and exceptions more object-oriented. One well-known
code example is the Microsoft Application Blocks Exception Handling block,
which should serve as an excellent starting point for beginning to intermediate-level
developers. Another excellent open-source offering is Log4Net, an extensive
and easy-to- use set of logging classes that's been ported from the JAVA
space.
Document your classes, and take the time to be exceptional coder!
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  | Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.
|