Hiya guys,

I recently read some recommendations on the GTK# mailing list regarding the use of 
exceptions.

The recommendation Microsoft have been giving is that exceptions should be avoided 
unless absolutely required.  The best way to report errors  should be done through 
return error codes.  The main reason behind this is speed. Microsoft has had a bad 
track record when it comes to  writing robust and secure applications and much of the 
reason behind this is because of the Microsoft mentality of "speed is best" (that's  
partly why they are plagued with so many buffer overflow exploits).  Using return 
error codes under the (often false) assumption that you'll  get a speed increase will 
mean that you may introduce subtle errors into your code.

Now, I'm not going to tell everyone how to write design or write *their* projects but 
I feel that some people aren't quite getting the full  picture with regards to 
exceptions and their use in a modern OO language.  I hope that people will spend the 
time to read this message and  come away with perspective that they consider useful.

Lets consider Int32.Parse.  The intended function of this method is to parse a string 
into an int.  The natural choice for the return value  would be the parsed int.  This 
allows us to use the method like so:

--------------------------------------------------------------

x = Int32.Parse(s);

// or 

SomeClass.Foo(Int32.Parse(s));

--------------------------------------------------------------

Now, how do we report when the string passed is badly formatted?  Firstly, lets 
consider using exceptions.  Using an "exception" enabled  Int32.Parse would look like 
tihs:

--------------------------------------------------------------

try
{
  x = Int32.Parse(s);
}
catch (ParseException e)
{
  Cosole.WriteLine(e.Message);
}

// or

try
{
  SomeClass.Foo(Int32.Parse(s));
}
catch (ParseException e)
{
}

--------------------------------------------------------------


Now, lets consider avoiding exceptions and instead replacing the return value with an 
error code and returning the real value through an  "out" parameter.    The new 
Int32.Parse would be used like so:

--------------------------------------------------------------

int e;

e = Int32.Parse(s, out x);

if (e > 0)
{
   Console.WriteLine("Error: {0}", e);
}

// Can't aggregate Int32.Parse as an argument to SomeClass.Foo

--------------------------------------------------------------


There are some problems with using the error code approach.  Some are clear, and some 
are more subtle.

1) Not having the result of the Int32.Parse in the return value prevents us from using 
Int32.Parse as an argument to another method.

2) Error codes don't supply the "rich" information that exceptions do.

3) Unlike exceptions, error codes don't force redirection of program flow when there 
is an error.  A program that doesn't check for an error  code could continue for 
several more statements until it crashes with a seemingly unrelated error.  Exceptions 
will cause an immediate jump  to the closet error handler for the exception.  If no 
error handler is available the program will terminate rather than continue running 
with  (almost always) invalid state.

4) Exceptions force programmers to mix program logic and error handling.  Programmers 
need to surround every method with an if/else  statement.  Forgetting to surround 
every method with an if/else statement can lead to errors as described in point 3.  
With exceptions, one  try/catch can be used to handle the exceptions of more than one 
statement.  

Here's a simple example of what I mean:


Lets say you have the following block of code:

--------------------------------------------------------------

x = Int32.Parse(s);
Foo(x);
y = Int32.Parse(t);
Bar(y);

--------------------------------------------------------------

Every statement can't be executed unless the previous statement suceeded.

Using exceptions, you'd end up with code like this:

--------------------------------------------------------------

try
{
  x = Int32.Parse(s);
  Foo(x);
  y = Int32.Parse(x.ToString() + "0");
  Bar(y);
}
catch (ParseException e)
{
  Console.WriteLine(e.Message);
}

--------------------------------------------------------------

As you can see, the code is almost identical.  Exceptions allow programmers to 
"program along the path of success" and then handle expected  errors centrally (at the 
end of the code block).  Notice how using exceptions here is useful even though the 
stack unwind feature of  exceptions isn't fully utilised.

Using error codes you'd get something like this:

--------------------------------------------------------------

e = Int32.Parse(s, out x);

if (e == 0)
{
   e = Int32.Parse(x.ToString() + "0", out y);

   if (e == 0)
   {
      Bar(y);
   }
   else
   {
      Console.WriteLine("Error {0}", e);
   }
}
else
{
  Console.WriteLine("Error {0}", e);
}

--------------------------------------------------------------

The code is *harder* to read and understand because error handling is intertwined 
within the code.  Also, notice how the error handling code  had to be duplicated (you 
could avoid this using goto...ugh...).  With error codes you *MUST* *ALWAYS* check the 
return code *THERE AND THEN*  otherwise you could end up using invalid data.  If you 
don't check exceptions *THERE AND THEN* you know that the remaining statements won't  
get executed with incorrect data.  Exceptions are clearer and safer.

An alternative to exceptions and return error codes that some people consider are to 
add a "Error" property to the object in question (e.g.  Stream.Error).  This approach 
introduces subtle race conditions when those objects are used in a multi-threaded 
application.  Those race  conditions can sometimes be avoided using synchronization -- 
but then that defeates one of the intended reasons for avoiding exceptions  (speed).

Here's an example of a race condition that I still see occuring:

--------------------------------------------------------------

Spot the race condition:

if (File.Exists(fileName))
{
  reader = File.Open(fileName);
}
else
{
  Console.WriteLine("error");
}

The following code doesn't have the race condition:

try
{
  reader = File.Open(fileName)
}
catch (FileNotFoundException)
{
  Console.WriteLine("error");
}

--------------------------------------------------------------

Using "state" to store error conditions is (IMO) a very bad idea.



Exceptions can be much slower than using error codes, however, it should be noted that 
the path of error is usually the least walked path.   Most of the time your code will 
not throw exceptions.  When exceptions aren't thrown there is *very* little overhead.  
In fact, the overhead  of using error codes can be *greater* than that of using 
exceptions because you have to surround a significant number of statements with an  
if/else.  Unlike exceptions, the if/else always takes CPU time; even when there is no 
error.

Unless you are using exceptions to report return values you'll rarely encounter 
performance issues involving exceptions because they will  occur rarely.  You'll get 
far more milage optimising your algorithms.  Remember, as a rough guide, 90% of your 
CPU time is spent in 10% of  your code!.  If most of your process time is spent 
throwing exceptions then I think you should seriously think about redesigning your  
application!  I think this makes a lot of sense; and it makes me to wonder what 
Microsoft were thinking when they recommended return codes  over exceptions because of 
*speed*.


Here's my own  personal recommmendations regarding exceptions:


+ Since all .NET methods can't and don't return error codes there are cases where 
exceptions *must* be used, therefore, be consistant and  always use exceptions to 
report errors except for the "hotspots" in your code which should be carefully 
examined and optimized *afterwards*.

+ Use return values for return values.  Don't use them to report errors.  Exceptions 
were invented so we could use 'return' the way it was  intended.

+ Don't use exceptions to report return values.  Use exceptions to report errors that 
prevent your method from completing successfully.

+ Don't use state (e.g. an error property) as the only way to report errors.  You'll 
get into trouble when threading.

+ If you really *must* use error codes to improve speed, you should supply a version 
that throws exceptions so that people aren't *forced* to  check your error code "there 
and then".

+ It can be quite useful to supply methods that complement each other.  File.Exists 
and File.Open are a good example.

e.g. The method File.Exists returns a bool (that makes sense since the purpose of the 
method is to check whether a file exists or not) but    File.Exists should *not* throw 
FileNotFoundException.  The method File.Open should return a TextReader and *not* a 
bool because the purpose  of the method is to open a file.  File.Open *should* throw 
FileNotFoundException.

I hope this post has been useful to people.  I have spent a lot of time thinking about 
this isssue and I believe it is important.  If anyone has any questions regarding this 
topic, please  email me personally (remember to remove the SPAM guards from my email 
address).  

All the best,

^Tum

_______________________________________________
Mono-list maillist  -  [EMAIL PROTECTED]
http://lists.ximian.com/mailman/listinfo/mono-list

Reply via email to