I keep running into long term c++ programmers who refuse to use exceptions
{ This article may look like spam at the beginning, and my spam filter thought
so, so if you're not reading this mod comment then that may be the reason. :-)
The article is close to 50 KiB, while our suggested [excess] rejection criterion
is >50 KiB. Please trim excess quoting from responses. -mod }
C++ Exceptions
Nomad: I am Nomad. I am perfect.
Kirk: I am the Kirk, the creator?
Nomad: You are the creator.
Kirk: You are wrong! Jackson Roy Kirk your creator is dead. You have
mistaken me for him. You are in error. You did not discover your
mistake. You have made two errors. You are flawed and imperfect and
have not corrected by sterilization. You have made three errors.
Nomad: Error. Error. Error?.. Kaboom!
Start Trek "The Changeling"
Let us hope that when Stardate 3541.9 rolls around, we will no longer
be "in error". For now, however, computer programs have lots of bugs.
Bugs = wasted time, and thus wasted money. And I mean wasted money,
millions and billions of dollars worth. Some famous computer
catastrophes are available at http://encyclopedia.laborlawtalk.com/Computer_bug.
My personal favorite being the flaw in the Mars Climate Orbiter
software causing the loss of a space probe to Mars that cost 165
million dollars. I love the space program. We computer programmers
can't keep making this type of mistake and maintain credibility. As if
we had any anyway. Many studies are showing that software projects are
failing at an appalling rate http://www.it-cortex.com/Stat_Failure_Rate.htm
What I have found to be the biggest single reason for wasted time and
money at coding time (design and specification failures are much more
costly) is the failure to detect when the program erred. This means
failure to check whether a function, the basic building block of every
computer program, succeeded or not. The second most common problem is
the failure to report an error when it does occur. And an outright
crash is not the worse type of bug because that is reported to the
user. Those happen and can be dealt with. It's the invisible failures
that stay in the program undetected that cripple your program for
years before they are detected. Many of us have had the experience of
going through some old legacy code and saying, "Oh my god! No wonder
this never worked right! Look at this!!!"
Error detection, handling and correction are often never even thought
of, and even in some of the biggest software houses are handled
sloppily. The reason for this is not programmer laziness. It is
because until recently programming languages did not have any
consistent, enforceable methodology for dealing with the unexpected,
with errors. This issue has been resolved by the concept and usage of
exceptions.
I will assume you are familiar with C++ exceptions and have used them
on occasion. If not, you might want to bone up on them:
http://en.wikipedia.org/wiki/Exception_handling
Some smart people thought it wise to include exceptions in the C++
language, and after trying them for about a day I was sold. When a
function fails its task, it invariably affects other parts of the
program. Like a crowded freeway when one car stalls, the entire
freeway jams up. When a function in a program fails, all the functions
that depend on that function are also at risk. Once must recognize
that when a function fails, the entire program enters a different
state. We don't just have an isolated failure. We have an entire new
state of the program that might be called chaos because the unexpected
has happened.
How to properly use C++ exceptions
What I'm going to show you is how to really revolutionize your
programming by using exceptions to make error handling as painless as
may ever be possible.
Exceptions will enable you to remove almost all error handling clutter
from your code, make your code more easily re-useable and much easier
to debug.
The exception mechanism is simple. Anywhere in your code where you
want you can "throw", like this:
throw "An error occurred here";
or
throw 22;
or
throw std::string("This is messed up right up in here.");
You can "throw" any data type you want, even pointers.
At other points in your program you "catch" anything thrown with the
catch() statement which catches anything thrown from within the try
area.
try
{
throw std::string("Ooops I messed up");
}
catch(const std::string& theException) // You have to "catch" the same
type you threw (or a reference to it, and it can be const)
{
//if any exceptions are throw by "do something", we come
here
}
That's it. That's the simple syntax of using exceptions. Though the
syntax is simple, how you use this is ultra powerful.
A simple example of how you might use exceptions:
FILE* FileOpen(const char* filename, const char* mode)
{
FILE* theFile = fopen(filename, mode);
if ( !theFile)
throw "Error opening file";
return theFile;
}
int main(int argc, char* argv[])
{
try
{
FILE* theFile = FileOpen("testfile.txt",
"r");
// Do something with the file
}
catch(const char* error_string)
{
// If FileOpen fails we come immediately
to here, skipping "Do someth ing with the file"
puts(error_string);
exit(-1);
}
exit(0);
}
If FileOpen() fails, it throws an exception which immediately unravels
all functions that were already called within try and executes the
code contained in catch.
C++ provides you this syntax of exceptions. It doesn't tell you how to
use exceptions well. I'll try to provide some help with that.
The first important rule to remember about using exceptions well is
that (with a few exceptions) you should only throw an exception when a
function fails to perform its job. The reason for doing this is part
and parcel of the philosophy for using exceptions and is embedded in
the very name of the concept: exception. Exceptions should be thrown
when the normal, expected flow of your program experiences an oddity,
an exception. What is normally called an error falls into this
category also.
The item thrown can be any class or anything that can be a variable.
For the sake of simplicity, in the examples above I threw a const
char*. For reasons I will get into later you normally want to throw a
class specifically designed to be thrown. Why? Because error
conditions are a sort of problem domain unto themselves and a class
specifically designed to capture information for that domain is best.
The single most important thing to remember about exceptions is the
try/catch system is s imply a way to return, to throw away, an
arbitrary number of stack frames. This means throwing an exception is
like a function return, but the return can span many functions deep
and return anything you want.
This is powerful because now you do not have to add error handling
code everywhere, only where you actually care about doing something
with the error.
That is the simplest, most general and accurate statement that defines
the purpose of C++ exceptions. Why would you want to do this?
To be able to throw away any amount of a program if something fails
because when something fails all the things that depend on that thing
will potentially also fail. This implies that an entire chain of
functions is at risk if any one of them fails. A chain is broken if
any one of its links are broken. If a link breaks, the entire chain
starting at the very first link must be thrown away and that failure
must now be dealt with (in the catch clause).
To recognize that error conditions present their very own peculiar
requirements and due to the importance of errors in computer programs
they need their own syntax and mechanisms to handle them.
First, I recommend you never throw the standard exceptions provided in
your library such as std::runtime_error. This is not because they are
badly written. It is because they are too general, not written for
your program. Let me explain.
Success in Object Oriented Programming is largely based upon creating
and using good components. The exception class you use might possibly
be the most important single class you'll ever use in your programs so
it's really important that it be good, or at least, not initially
flawed because a good class can be subclassed later to add features.
You don't want to use an exception class that has built in limitations
and the standard exceptions have built in limitations, mainly, they
are what they are and you can't change them to suit your application.
If you change them, they are not longer the standard exceptions!
You should create your own exception class subclassed from
std::exception. (In fact there is a general principle here that you
should never use the classes provided by someone else directly because
they will inevitably be lacking in some feature you need later, but a
more complete discussion on that subject I'll have to put off for
another day). For example, here is the simplest Exception class you
can get away with that subclasses from std::exception
#include <exception>
class Exception: public std::exception
{
};
This is a bare minimum but covers your ass by enabling you to retrofit
features later as you come to need them. You could use this in your
program and add more features to it later as they become necessary.
Something more useful would add a message that could be displayed
later when the Exception was caught. Displaying the message should be
done with the what() function because if you are catching
std::exception, which you should be, its what() function is virtual so
that any class derived from std::exception that gets caught will use
its what() due to the magic of virtual functions.
#include <exception>
#include <string>
class Exception: public std::exception
{
std::string Message;
public:
Exception(const std::string& message)
: Message(message)
{
}
const char* what() throw() // throw() required if your
STL std::exception has it.
{
return Message.c_str();
}
~Exception() throw() // Here only to please some
compilers
{
}
};
And there are other good reasons why you need to use your own
exception class. Notice, all of these things are optional. The power
of your exception class depends only how much you choose to add.
Having the source code to your own Exception class, you can put a
breakpoint when debugging and catch any exception in the debugger
right when it happens.
You can write the exception text into a log file to help debugging.
You can add a stack trace to the exception information that greatly
helps debugging
You can look up error detail information and add it to the exception
string to help debugging (i.e. strerror(errno))
Because you have the code, you can retrofit features you find out you
need later, or even subclass again to add more features.
Since you inherit from std::exception your class is an std::exception
and can be cau ght wherever an std::exception is used and so is error
compatible with other code that uses std::exception
You can redirect exception logging anywhere you want by adding the
appropriate code in the Exception constructor, like to syslog for Unix
style programmers or you can help debug a customer problem by having
them send logging over a TCP connection to you from anywhere in the
world.
And in general you can do anything you want when an error occurs by
adding that appropriate code into Exception::Exception.
Once you have this basic exception class you can proceed to using
exceptions. This next section will cover common programming Diseases
and how exceptions cure them.
Problems caused by not using exceptions and how to fix them
Disease: Programmers pathologically ignore or forget to check return
codes from functions and now there are hard to find bugs in the
program.
Until exceptions were invented the popular way to indicate a function
failure was to return an error code, but this method depended upon the
caller checking for the error value. This was more often than not
ignored and thus programs failed seconds or minutes later for
apparently unknown reasons and debugging was very time consuming (i.e.
expensive) because the symptom of the error occurred a long way from
its cause and one had to be a detective to track down the problem.
Using error codes there was no mandatory requirement for the caller to
check the error code. The returner of an error code is really at the
mercy of the caller to ensure the error is dealt with. Experience has
taught that the vast majority of function calls are not checked for
error status leaving a hidden point of failure in your program that is
time consuming and costly to track down when it does fail. It isn't
the common errors that occur a lot that kill you; it's the ones that
occur ran domly and are hard to reproduce that destroy your product.
If you signal an error, i.e. a failure of the function to perform its
task, by throwing an exception, a programmer must deal with it. It
cannot be ignored by accident.
A few macros can really help you nail down ALL error handling:
#define THROW_IF_ERROR(CODE) if ( (CODE) == -1 ) throw
Exception( #CODE ": failed");
#define THROW_IF_NULL(CODE) if ( (CODE) == NULL ) throw
Exception( #CODE ": returned NULL");
Now you can easily retrofit all those old C functions that use old
fashioned error return codes:
THROW_IF_ERROR( printf("Your momma dont dance and your daddy dont rock
and roll") );
THROW_IF_NULL( file_handle = tmpfile());
This makes it easy for programmers to check errors. You must give them
every tool possible to do this if you want good error handling policy
in your project.
Disease: A clean and beautiful algorithm is made obscure by messy
error handling code.
Until exceptions came along, programmers didn't mind adding all kinds
of if statements to their algorithms that had nothing to do with the
algorithm itself; all kinds of "if (error) then (do this)" statements
which themselves are a good source of bugs in a program. Checking
error codes is onerous and fills nicely written functions with error
checking code that contributes nothing to the primary purpose of the
function. You've got a significant portion of the function dedicated
to handling failures, when failures occur in an insignificant
percentage of the time. That is, you've got perhaps 30% of your code
(if you are truly checking each function call for success of failure)
devoted to .001% or less of the actual run time of the function.
Imagine you are walking down the street and every step you take,
having to look down and see if your foot actually made it! If
statements are also murderous on the execution speed of the program
because the processor must throw out of its pipeline any pre fetched
execution path that wasn't taken.
Users of exceptions know that the big negative of using if statements
to check return codes is that you are wasting time even when things
are going well. Exceptions make it so that you only spend time when
things are going wrong.
If your function, calls other functions that themselves throw
exceptions for error handling, you don't have to add any error
handling code in most cases. Most of the time all you are doing is
passing a failure back to a caller higher up the stack. If you are
using exceptions, this is done automatically for you and you need to
add no code at all. In practice, only a few high level functions
actually need to catch exceptions. Functions inherit their error
handling and most don't need explicit error handling code. Unnecessary
because you will find when throwing and catching exceptions, you only
throw where the error actually happens and you only catch where you
want to handle. This is almost always several functions away meaning
all intermediate functions need no error handling at all. They inherit
their error handling.
Using the analogy of walking down the street, you only react if
something out of the ordinary occurs: your foot hits something and
throws an exception up to your knee. Your knee doesn't know how to
handle this so it doesn't catch the exception but allows it to
propagate to your spinal chord which, in the same vein, doesn't know
what do so allows the exception to propagate to your brain which DOES
know what to do. So your brain catches the exception and deals with it
and you don't have to explicitly check each and ever step you take.
You only get alerted if SOMETHING GOES WRONG, NOT IF SOMETHING GOES
RIGHT. The only reason you have to add any throws at all is because
you are talking to old fashioned operating system calls that return
error codes which you have to translate into a throw exception in your
more modern C++ code.
This problem of code intending to do one thing being cluttered with
code doing other stuff is so annoying to programmers that an entire
programming approach has been invented called Aspect Oriented
Programming: http://en.wikipedia.org/wiki/Aspect_Oriented_Programming
Using exceptions is very effective in removing clutter from your
functions as regards error handling code. You won't need Aspect
Oriented Programming to deal with this.
Disease: Indecision and disagreement about how to deal with errors in
a program.
Using return codes to signal errors makes the programmer try to come
up with a return value that makes sense (at the time) but whatever he
picks won't necessarily be the same that someone else decided to use
in the rest of the project. Return code conventions are inconsistent.
For example the Windows API has a wide variety of conflicting and
inconsistent return codes to indicate error. Some functions return
true, others return false, others return an error value, some return
0, some return NULL, some return HRESULTS. The UNIX C functions are
not much better. Over the years programmers have just accepted the
fact that things are this screwed up. This is not a minor issue. Many
bugs can be traced to programmer confusion over return codes and a lot
of programmer time is consumed having to constantly look up what the
function return value means in the documentation. How many of you know
what printf() returns to indicate error? mkdir()? chdir()? stat()?
Also realize the mental energy the programmer wastes by having to make
a stupid, and what he knows is really a useless decision about what
error code to return. When a programmer is limited to return values as
error indicators he has a constant problem while coding.
Programmer's'Brain ->"Here is a place that could fail. What do I do?
Return -1? Return NULL? Return 0? Return an error value? Return false?
Try to recover? How do I clean up what I already had in progress?
Damn, now where was I? What was my function doing?
Ok now I'm using a function someone else wrote. What error does this
function return? Should I check it or just hope for the best? Didn't I
just go through this same thought process the last function I wrote?
Why doesn't someone do something to fix all this wasted time and
energy I spend every time I write a function?"
Exception-using programmers gracefully handle errors using exceptions.
They simply throw Exception when their function cannot fulfill its
task. They don't have to ponder how the caller will handle this. A
program or library that uses exceptions based upon std::exception is
instantly future error compatible with other code using the standard
exceptions so he doesn't have to spend 50% of his brainpower trying to
figure out what to do during errors which are .001% of the instances
of his function being called.
You will also get programmers on a team fighting about this kind of
stuff. That's bad. Having a standard exception class makes it easier
to reach a consensus because exceptions DO work for handling error
conditions so you can reliably order them to do so and be confident
your decision will work out in the end.
Disease: Return codes cannot provide enough information about an error
in order to decide how to handle it.
Return codes report next to nothing about errors. Error reporting done
from error codes is next to useless as far as informing us what the
error is. Error codes can only be a single number and thus can only
display pre-canned system messages such as "no access" or "parameter
failed" when in reality the error should report much, much more such
as "Unable to open file foo.bar due to permission errors, it is not
owned by you" or "Parameter 5 passed to function foo() was 27 which is
out of the allowed range for this parameter". Return code programmers
stay late at work trying to figure out what happened.
Exception users give full and accurate information about their errors.
Exceptions can be any type of object and can report any information
necessary. A nice, well rounded exception message might contain
information like this: Windows Error(10045) Connection Timeout
connecting to www.testpatch.com at location TCPConnect.cpp line 245
which gives you instantly what would take an "error code" programmer a
half hour to gather. Don't waste time figuring out what happened, have
the computer do it for you. That's what it's there for. When you
construct your exception you can have strerror() put the string
version of the system error in the exception.
Disease: An error occurs in a function that cannot return a value.
Some functions cannot return error codes. Constructors, conversion
operators, and operator overload functions cannot return a success or
fail value and thus cannot report a failure using error codes.
Functions that return useful values such as the product of a
multiplication cannot return an error value.
One way programmer work around failed constructors is by having the
constructed object mark itself bad by perhaps a bool Bad data member.
Then check it later and not use it. This is really bad because, if an
essential object fails to construct, the entire function which uses it
must certainly also fail. There is no reason to continue past a failed
constructor but if you aren't using exceptions you must now add all
kinds of if (object-bad) garbage, often bigger in size than the code
of the constructor itself, to handle its failure. You must also
manually deconstruct anything that was constructed in this constructor
and do all the accounting necessary to keep track of what was
constructed and what wasn't.
Using exceptions you simply throw if the constructor fails. Let the
code that attempted to construct this object worry about what to do. C+
+ will automatically destruct any half constructed object for you.
Disease: Return codes corrupt the meaning of the word "function" and
destroy the code's usability as a function
Mathematically speaking, the definition of a function is this: http
http://en.wikipedia.org/wiki/Function_%28mathematics%29. It's
important to note that a function translates multiple inputs into
exactly one output. This means that the "result" of the function is
the output, or in computer science, the return value. Why is this
important? Because it implies that as you expand the functionality of
a program, you can substitute in a "dynamic" value (a function) where
there was once a static value (a simple variable).
For example, you can print someone's name:
Print("Jim Johnson");
Or you can print a transformation of the name more suitable to your
needs.
Print LastNameOnly("Jim Johnson");
The latter line uses a function which, while being handled like a
piece of data, actually computes a changing, dynamic value. The real
power of the computer comes when doing many things with the same basic
pattern, but slightly different details. That's what functions can do
for you. However, functions can fail. They may be unable to compute a
return value for whatever reason. Programmers who use return codes,
destroy the utility of a function by returning a "success" or
"failure" indicator instead of the actual product of the function, in
this case, just the person's last name.
So if all you had available to deal with errors was returning a
"special" value meaning "failure" the above simple code would become
something like this (Here the programmer outputs the result into a
variable he has already declared and uses the return value of the
function as a success or failure indicator).
String LastName;
bool success = LastNameOnly("Jim Johnson", &LastName);
if (success)
Print LastName;
else
DealWithError()
Wow. You've gone from one line of code, to 6, effectively sextupling
the complexity, bug probability and learning time for this code. This
may not seem like much but if you multiply it by the thousands of
functions in a program it becomes quite significant. Instead of a
statement, you now a program snippet that is not understandable at a
glance. 5/6 of that snippet is devoted to error handling for a
situation that might only occur 1 out of a million times in actual
practice (person not having a last name). You are asking every
programmer who ever reads this to spend 5/6 of his brain power
thinking about something that might happen .00001 percent of the time.
Disease: Errors are occurring in the program but where to look in tens
of thousands of lines of code?
Without good exceptions, a programmer would handle it this way: He
tries to guess what is happening and places breakpoints at the places
that he suspects problems are happening. How much time and money do
you want to waste on programmers guessing where to put breakpoints?
There is no single place, object or module where errors get handled.
This means there is no single place the programmer can inspect, log,
measure, set breakpoints or display errors.
If you use a hierarchy of exceptions to signal errors, you know
exactly where, when and how errors are occurring in your program at
all times. How? Because all errors in your program call the base
exception class constructor and there you can log, display, measure
and set breakpoints. In my experience, this is in fact the first
benefit most programmers notice when they start to use exceptions and
one way to convince them to keep using exceptions. It is also a
reason why you never want to use exceptions for trivial reasons. Doing
so would make a constructor breakpoint like this break too often to be
useful.
Disease: Errors are occurring in the program out on customers'
computers off site.
If all you have is return codes and a chaotic error handling system
you have to tell your boss: "I can't debug this unless I can put the
code in a debugger to see what's happening". Error messages are
missing or so incomplete that the user cannot fix the situation nor
can the programmer even understand what is going on remotely.
Users of a full featured exception class use full information in their
errors so that often the error messages are descriptive enough for the
user to fix his problem. Also, since all errors channel through the
same function, the base exception class constructor, you can tell the
user having a problem to turn on the logging and save a trace of the
errors occurring offsite or even have the code send log messages
across The Internet immediately so you can figure out exactly what is
failing!
Disease: Some code is moved to another project and needs to be
integrated into its error handling scheme.
Return coders know this project will be a nightmare. If the new
environment doesn't use the same conventions they did to do error
handling, there will be a lot of rewriting, debugging, testing,
crashes and customer frustration to deal with. Error codes cause the
caller and the callee to have to agree upon a particular set of
conventions, error codes, which makes both inflexible for use in other
situations.
Using exceptions based upon the same standard subclass,
std::exception, makes transferring libraries, classes and programs to
other project completely error compatible. Even if the moved-to
project uses a different exception hierarchy, catch clauses are all
that have to be changed, and they are usually very few in most
programs.
Disease: Hardware errors throw exceptions. Access violations, divide
by zero, stack overflow and other usually fatal conditions are
signaled by exception throws in many operating systems. If you aren't
prepared to deal with exceptions, all your program can do is die
suddenly.
If you are depending on return codes to handle these, too bad. You're
screwed. Operating system will throw up an error message box and exit
the program. Imagine a heart monitor program Access Violating and
simply FAILING without at least notifying the nurse on call that
something is wrong.
Hardware errors are exceptions that can be caught just like any
others. In Unix style OSs you use a signal handler, and in Windows you
use _set_se_translator() to catch hardware exceptions and turn them
into C++ exceptions. Then you can actually do something useful like
print a stack trace to help debug or ring an alarm to ask a human
being to come and help. In many cases if a thread has a hardware error
you can catch it without crashing the entire program. In all cases you
can catch the hardware error and deal with it in your own way instead
of letting your operating system terminate the program.
Disease: The need to signal an error condition in code that has no
provision for dealing with errors because when it was written no one
expected that an error could occur here.
If you are stuck using return codes there is little you can do except
rewrite all the functions involved. Perhaps you can log the error and/
or put up a message box informing the user. Forget about doing
anything to actually handle the error. Retrofitting good error
handling with return codes on already written programs is next to
impossible. When the failing function is 10 levels deep and all the
functions above it already have their return values being used for
other things because the original programmers didn't expect anything
to fail, there is no way to return that error condition up the stack
to someone who can do something about it. In the real world, the
programmer just gives up and hopes the error doesn't occur. Of course
it does, it fails silently and the program behaves strangely and it
takes days to track down what is going wrong with it.
Disease: Error conditions have no consistent strategy for dealing with
them.
Exceptions give error handling its own set of tools more suited to the
conditions error handling has to deal with. Code that uses exceptions
has its own domain especially designed for handling errors.
Retrofitting a new error condition on a program that uses exceptions
to report errors involves very little new coding: just throwing the
exception somewhere, and catching it somewhere else. And often it is
not even necessary to write a new catch clause because an existing one
already does the job. The point is, all you do is throw. The handling
of the error is up to the higher level code which he can look at
later.
Disease: Functions tangled with return code checking, propagate their
problems to anyone else using them, spreading complexity like a
cancer.
If the members of your class know how to copy themselves, then your
class doesn't need a custom copy constructor. The same is true for
many other common functions like constructors, destructors, operator=,
operator== etc. However, if any of those classes don't use exceptions
to signal failures, you cannot rely on these auto-generated functions
and you must write custom versions. A real example from my last job
was a situation where a class had a "File" member. In my class's
constructor initialization list that File class was constructed like
this File("datafile.txt"). However, this File object's constructor was
failing INVISIBLY because it did not use exceptions to signal a
failure (it depended on the programmer to check a "good" variable). It
took us hours to track down what the problem was. This is a clear
example of why you should not rely on return codes even in classes
that you consider trivial. The programmer's excuse in this example was
"that's how the iostream library works". The iostream library, bless
its heart, was written before exceptions were part of the language. I
suggest if you use it, wrapping it in your own classes that do throw
exceptions if failures occur. It's hard to get people to use
exceptions if parts of the standard library do not, but that doesn't
mean they are correct. They aren't exempt from the problems I describe
above but their current state, the state of all "standard" c++
libraries reflects the battle we have going on between old fashioned
programmers who refuse to use exceptions, and those who have seen the
light and do.
-
Disease: Callbacks that offer no way to signal their callers
A common way to operate on items in an STL container is to provide a
callback function to operate on each item of the list. The Standard
Template Library does this with functions such as "transform" or
"for_each". (Yes, there is a for_each
http://www.cplusplus.com/reference/algorithm/)
You provide a "functor" which operates on each member of the list.
What happens if you want to stop the process early? Like you want to
use for_each to search for an particular item and finish the
iterations when you find it? What happens if there is an error during
this process. The STL ignores the return value of the functor so you
cannot signal an end that way. Solution? Throw an exception. The
entire iteration will be aborted and you can catch this exception and
resume your work from there.
Disease: Error handling code doesn't get tested.
Your program is testing and goes into production but you still get
crashes. What the heck? One reason this happens is that error handling
code, code that executes only when errors happen doesn't get tested as
well as the main code. Why not? Because to test an error, you have to
make that error happen and black box testers can't always make this
happen. If you are using exceptions to handle errors you can do this:
Error handling code is hard to test because it only gets executed
during errors which are often rare, however, there is a way, if you
are using try/catch to easily test your error handling code. Say you
have some code like this:
try
{
socket.Connect("127.0.0.1:80");
}
catch(const std::exception& ex)
{
delete socket;
Log("Could not connect to %s");
}
There is a bug in the error handling code (no argument for the %s).
What you can do is go through your code and replace catch() with a
macro CATCH()
try
{
socket.Connect("127.0.0.1:80");
}
CATCH(const Exception& ex)
{
delete socket;
Log("Could not connect to %s");
}
Now, define CATCH(X) catch(X) {};
So now your code expands to this:
try
{
socket.Connect("127.0.0.1:80");
}
catch (const Exception& ex)
{
}
{
delete socket;
Log("Could not connect to %s");
}
Which makes the error handling code ALWAYS EXECUTE. Now you run your
code. The catch block you wrote to handle the error ALWAYS gets called
and tested. When you've tested it, you remove the CATCH macro on that
single piece of code and run the program again exercising the NEXT
catch block. Keep doing this until all your error handling code (catch
blocks) is tested.
Disease: Programmers killing a thread instead of exiting it cleanly.
Even though I said exceptions should only be used to handle error
conditions, I'm going to backtrack now. One of the neat things about
computer programming is that the inventors of something are often not
the best users of that that thing! Sometimes you can find novel uses
for something never intended to be used that way.
If you have ever programmed using multi-threading you have come across
the problem of "how do I properly stop a thread that is running?". You
cannot just stop the thread by killing it, you must somehow make the
thread return to its starting point so that all intervening resources
it was using are cleaned up. If you just kill the thread using the
operating system facilities you can leave memory leaks, open files,
unreleased mutexes and all sorts of nasty messes.
The solution to this is to somehow make your thread throw a special
exception, not derived from std::exception because you don't want it
caught by any normal error code. You only catch it in your thread's
opening function before you did anything significant that might need
cleanup. I will write another article on this at another time.
Here is a rich but simple exception class that I recommend you start
using as soon as possible.
#include <exception>
#include <string>
#include <cerrno>
// This STRINGIZE mumbo jumbo is necessary to turn __LINE__ into a
string using only macro syntax
#define STRINGIZE(S) REALLY_STRINGIZE(S)
#define REALLY_STRINGIZE(S) #S
// The ErrorLocation() macro will turn into the file and line where it
is used, like myfile:234
#define ErrorLocation() (__FILE__ ":" STRINGIZE(__LINE__))
class Exception: public std::exception
{
int SystemError;
int ApplicationError;
std::string Location;
std::string Message;
mutable std::string What; // Necessary kludge to deal
with std::exception being lame and returning const char* for what()
public:
Exception(const std::string& message, const std::string&
location = "", int systemError = 0, int applicationError = 0)
: Message(message), Location(location),
SystemError(systemError), ApplicationError(applicationError)
{
// If the caller doesn't set the error,
derive it from the last error the system is aware of
if (systemError == 0)
systemError = errno;
}
const char* what()
{
char errorString[1024] = "Unknown";
// If strerror fails, errorString will
remain set to "Unknown"
strncpy(errorString,
strerror(SystemError), sizeof(errorString));
What = "What: " + Message + "\n" +
// Only display system error system if
there was one (non-zero)
(SystemError > 0 ? std::string("Why: ") +
errorString + "\n" : "") +
// Only display location if we had set it
in the constructor
(Location.empty() ? "" :
std::string("Where: ") + Location + "\n");
return What.c_str();
}
~Exception() throw()
{
}
};
For simplicity's sake I did not add certain things like access
functions to get the Location and Message if those are the only ones
you might want to display. You can add those easily enough if they
become necessary.
The what() function is there so if you catch(const std::exception& ex)
calling ex.what() will show the more complete error information in
class Exception.
Then you can actually throw an exception like this:
FILE* file = fopen("20th_Century_French_Battle_Victories.txt");
If (!file)
throw
Exception("20th_Century_French_Battle_Victories.txt",
ErrorLocation());
Now, when you catch that exception, and use the what() function, the
error string will be something very complete like:
What: 20th_Centry_French_Battle_Victories.txt
Why: File does not exist
Where: FindFiles.cpp:244
This saves you minutes looking up error codes etc. and helps not break
your concentration while debugging, all in a few lines of code.
Now that you have a good exception class, how do you use it?
The most important thing about using exceptions is to be very clear
about what an exception is. If you want your code to make the jump to
light speed you can't go halfway with exceptions. A lot of programmers
are stuck between the old and new paradigms. I hear this quite often
and it's a problem, "Exceptions are for fatal errors but I still use
return codes for some things". This is not going to work nearly as
well. A very simple and clear criteria for using exceptions is, throw
an exception when a function fails to perform its function. This
means, no more error code returns at all, none, nada, zilch.
When I bring this up with some programmers I use the fopen() argument.
If you have a function that opens a file, should it return a valid
file handle if it succeeds, and NULL if it fails? If you said return
NULL you aren't getting what I'm saying. If your program is depending
on the file to open and it fails, that's the exact situation that
requires an exception.
A failure to perform its task IS fatal for that function. You cannot
know ahead of time how serious a failure of your function will be to
the program using it. Exceptions were invented to fix problems with
error handling. If you don't use them you are stuck with the same old
problems.
You need to use this definition of Exception, "An exception occurs
when a function cannot accomplish its expected task".
C++ Deficiencies
Compared to some languages, C++ provides only very basic exception
capabilities, however the capabilities it does provide allow you to
build almost all the advanced capabilities other languages include by
default in their exceptions. One thing that is difficult to add for C+
+ exceptions is a stack trace. This is because there is no standard
for stack traces in the language. You must add your own and this is
problematic because different co mpilers and different processors
treat the stack very differently.
In later articles I will show you how to add stack traces to your
exceptions but that is beyond the scope of this article.
Bad ways to use exceptions
I've seen a lot of strange code in my day from programmers who weren't
really sure how to use exceptions. Once you understand that exception
throwing is simply a way to return up the stack an arbitrary distance,
everything else falls into place. Here are some examples of how NOT to
use exceptions:
Bad Use: Never throw and catch an exception in the same function.
Exceptions exist as a new and revolutionary way to signal function
failures. But some programmers are using them to create a "cleanup"
routine at the end of a function. Throwing and catching in the same
function is a sure sign of improper exception use. It means you
haven't gotten the idea that a failed function, fails by throwing an
exception that is caught by someone else.
FILE* afunction(void) //VERY WRONG and just plain redundant
{
File* file = fopen("afile.txt", "r");
try
{
if (!file)
throw Exception("Could not
open file");
}
catch(Exception& e)
{
return NULL;
}
return file;
};
This is really silly. And all permutations of this type of thing are
silly also. Because they are simply returning an error code anyway or
catching the exception and dealing with it, meaning, the function did
not fail, it was able to handle the situation. Never throw and catch
an exception in the same function! Exceptions should be used to signal
a failed function. If you use exceptions for trivial conditions that
aren't function failures you will end up increasing the clutter in
your code. If your function fails, throw an exception, don't catch it
and translate back into the bad old return code habit. The above
function should be written like this if you insist on using return
codes:
FILE* afunction(void) // Bad, old fashion but at least not a corrupted
use of exceptions
{
File* file = fopen("afile.txt", "r");9
return file;
};
Bad Use: Don't "half-use" exceptions
Many programmers, and even the standard library, have the idea that
exceptions should be reserved for "fatal" errors only. They still use
old style return codes in many functions. There is a huge flaw in the
logic of this approach. A failure to perform its function is fatal to
the function involved. Only the catcher of an exception can know if
the exception is fatal to the program or not. You cannot possibly
predict 100% how your class will be used. If your class has any
success at all it will be reused in ways you never imagined. If you
use an exception class based on std::exception you will not limit
future users of your class.
For short utility programs, dying if an exception is thrown no big
deal, but the bigger or longer running a program is, the more likely
errors must be recoverable. If you are writing pacemaker software,
every error must be recoverable. However, the fatality of the
exception isn't decided by the thrower, it's decided by the catcher of
the exception. If you are programming a library of code that is
intended to be re-used (and why aren't yo u?) it isn't up to you to
decide how an error in your library will affect the program using it?
Use exceptions and let the user decide.
Bad Use: catch(?)
Normally you never want to use catch(...). This catches ALL exceptions
but you cannot know what type of exception was caught or any
information about that exception. Often programmers say to themselves
"oh well, if anything happens here I don't care, just ignore it". This
is not in itself a problem but as code grows, that original code being
try/catch protected might grow and often you will need to know what's
going on and cannot simply suppress errors. There is also this factor:
when you ship a product it's better that it continue to run and
recover wherever possible. Users are very critical of obvious crashes
and might not even notice subtle errors so
One solution is to catch(?) in shipping code so your users see fewer
of your warts and perhaps the program can limp along still functional,
however, during development you never want to suppress errors. Using a
macro for catch(?) will do the trick:
#ifdef _DEBUG
#define CATCHALL catch(const Exception& e)
#else
#define CATCHALL catch(?)
#endif
Bad Use: Not having exception safe functions
When you program using exceptions there is one slight change you have
to make in your style. Your code must be written so that it can handle
an exception being thrown any time your function calls any other
function that might throw an exception. When an exception gets thrown
your function will be exited immediately at that point. Any local
variables will be destructed just as in a normal return. Remember, an
exception occurring is basically just a return occurring as far as
your programming approach. This is actually very easily done by simply
declaring certain variables using auto_ptr which is part of the C++
standard library. I will write an article dedicated to this in the
future but you can research that yourself if you demand immediate
satisfaction.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]