Author Topic: Exceptional Programming  (Read 8324 times)

Gillius

  • Administrator
  • User
  • *****
  • Posts: 147
    • http://www.gillius.org/
Exceptional Programming
« on: April 13, 2005, 09:28:24 AM »
I'm thinking about making GNE more "exceptional."

While working on muumi and Holomorph's problems that they mentioned about GNE, I had a problem trying to open connections with GNE (discovered later that it was due to my firewall).

What I noticed was that some of the basic GNE operations return only bool, such as the connect, listen, and open methods.  When this happens, currently there is no way to obtain any error information; in fact, there is not even any error information written to the debug log.

I wonder, then, at this time why did I not originally throw exceptions, espically when I have the perfect exception class (LowLevelError) already.  At minimum I could have returned an Error object from the methods.

I'm thinking that the reason why I did this was to not force the usage of exceptions onto the user if they did not want to use them.  However, I already do this to some extent in the packet parsing code.  I do know that my original goal for GNE was to construct a networking library using the latest cutting-edge C++ features and OOP techniques and designs, and that was how I was to differentiate GNE from the other libraries.  I realized from the start that a significant number of programmers would drop GNE immediately because of its use of Boost, templates, exceptions, multithreading, or heavily OO design, so I decided for those people I would take full usage of any feature even if I was afraid it would scare off some.

So what I want to do, I think, is convert GNE to be an "exceptional" library and make the methods like open, listen, connect, initConsole, initGNE and etc to throw exceptions on error rather than return error codes, so that the programmer can use exceptional programming techniques and also be sure that they cannot forget to simply check an error code and cause a crash.

However, I am a realist, and since GNE has been out for awhile, I know that even though I've said the API is not at all fixed until GNE 1.0 and may change at any point that GNE has been out for awhile so there may be some who are strongly opposed to this change, so that is why I write this post -- to get feedback (if any).  There are two compromises, write versions of GNE methods like "listenThrows" and "openThrows" that are exceptional versions of their counterparts, or change open, listen, etc to return Error instead of bool.

Does anyone have any opinions on this?
Gillius
Gillius's Programming http://www.gillius.org/

muumi

  • User
  • *
  • Posts: 9
Exceptional Programming
« Reply #1 on: April 17, 2005, 04:33:45 PM »
I believe that exception-handling should be used to handle errors and it would be great to have that functionality in GNE. Code should however be written in a way that allocated resources are always freed and that applications can continue to run even in case of errors. Simply failing to connect to a server should not terminate the application. Errors that don't stop applications from working should not therefore throw exceptions. Exceptions should be thrown only when an error prevents an application from running. If an error occurs it should be handled in a way that it won't stop anything from functioning. There are cases where this is not possible and only in these cases should exceptions be thrown. Some functions, such as destructors, should be guaranteed to be fail-safe. Personally, I don't like the tought of having both exceptional and non-exceptional versions of functions included in the library. Did you mean by returning an Error that functions would return error-codes when errors occur? I think it's a better solution than returning a bool but still crappier than having exceptions thrown. Writing errors to a log would be nice.

Gillius

  • Administrator
  • User
  • *****
  • Posts: 147
    • http://www.gillius.org/
Exceptional Programming
« Reply #2 on: April 17, 2005, 10:45:24 PM »
I don't think I agree that exceptions should only be used for errors that terminate the program.  Exception handling is a way to pass errors up to the code that deals with it.  In other languages that provide standard libraries, exceptions are thrown in many cases where they could be handled.  Take for example the java.net library for Java that does throw exceptions on errors like "I can't connect."

Exceptions allow the programmer to write code in a positive way rather than a negative one.  Without them, it is up to the programmer to check the error codes -- what if they forget and are lazy?  Seriously, if you've seen code 99% of them will ignore error codes returned by functions that are unlikely to fail.  Without exceptions, the program just segfaults or something horrible -- with exceptions you know that you only have to write the error handling code in one place, one time, for all places were errors might occur.  It also separates error handling code from logic code, improving readability.

Using destructors and RAII properly ensures that resources are cleaned up in case of errors and exception propagation, so that is the solution to that problem.  Resource cleanup is automatic and guaranteed in a properly written program.

I agree that destructors should never throw exceptions.

When I said "returning an Error" I meant literally changing the return from bool to an object of type Error (one of the types of Error is a "no error").

I disagree that GNE should write to the log as the primary way to communicate an error -- the calling code has no way to interpret that error to try to resolve it by trying some other code (like connecting to a secondary, fallback server), or notifying the user with an error message.
Gillius
Gillius's Programming http://www.gillius.org/

muumi

  • User
  • *
  • Posts: 9
Exceptional Programming
« Reply #3 on: April 18, 2005, 07:22:56 AM »
Quote
I don't think I agree that exceptions should only be used for errors that terminate the program

The errors themselves do not need to terminate the program but they should be so severe that at least portions of the application stop functioning correctly.

Quote
Take for example the java.net library for Java that does throw exceptions on errors like "I can't connect."

The "I can't connect" -error is actually just a failure to connect to the server. It is a common 'error' and will occur very often. It is expected that the connection might fail. It is not an exceptional situation. You should learn to distinguish between errors and failures to complete a task. Failing to complete a task is not always an error. Errors stop something from working correctly.
Let's take a function that connects to a server as an example. Let's call it connect(address, port). Preconditions for this function are that the provided arguments (address and port) are valid. If the preconditions are met we make an connection atttempt, otherwise we throw an exception. The connection attempt can both fail or succeed and there is nothing that requires it to succeed, so no matter if it fails or succeeds we throw no exceptions (an 'error' could be written to the log). We are still in a perfectly valid state, haven't broken anything and can produce a valid return value, so the connect() function succeeded even if it failed to connect to the server. It failed to complete it's task. Now it is up to the user of connect() to determine if the application can function correctly even if connect() failed to complete it's task. If it is unacceptable and breaks the functionality of the application then the user should throw an exception.

Quote
Seriously, if you've seen code 99% of them will ignore error codes returned by functions that are unlikely to fail

This is exactly why I prefer exceptions over error codes.

Quote
Without exceptions, the program just segfaults or something horrible

And these errors are the ones I suggest should throw exceptions.

Quote
I agree that destructors should never throw exceptions.

I was trying to point out that some functions should be guaranteed to be fail-safe. See David Abrahams exception safety guarantees (basic, strong and nothrow).

Quote
When I said "returning an Error" I meant literally changing the return from bool to an object of type Error (one of the types of Error is a "no error").

Maybe Error should be called Result instead.

Quote
I disagree that GNE should write to the log as the primary way to communicate an error

I never wrote that it should be the pirmary way to report errors - just that it would be nice to get errors written to a log file too.

Gillius

  • Administrator
  • User
  • *****
  • Posts: 147
    • http://www.gillius.org/
Exceptional Programming
« Reply #4 on: April 18, 2005, 12:03:09 PM »
You say that connect shouldn't throw because it failed to connect, but you said that you prefer exceptions to error codes?  So I am confused.  If a connection, open, bind, or whatever fails, you should pass back some sort of reason so that the application can react.  If you don't use exceptions, then you must return an error code.

If the connect fails but the user ignores the error code, the code makes the assumption that a connection occurred and eventually that assumption will lead to a worse error by calling some function without meeting its preconditions.

The Error class is named Error as it was meant to serve as the base for all exceptions and failures.  So far it has not yet been used as a return from any method or as a replacement for error codes, although it could be (and is why I proposed it).  There is a type of Error for not-an-error, but was provided just internally so that I could create some temporary values and I wanted to have a default.

I did read David Abraham's document on exception safety and it was a great read.

So that I know exactly what you mean, can you please show me example code that would use your view of the ideal interface?  Since you say you don't like error codes and exceptions aren't always appropriate I'm not sure what option is left.

Code: [Select]
//pseudo
try {
  new ClientConnection c;

  c.open( address. params )
  c.connect()
} catch ( Error& e ) {
  cout << "Connection to " << address << " failed, because of " << e << endl;
}
Gillius
Gillius's Programming http://www.gillius.org/

muumi

  • User
  • *
  • Posts: 9
Exceptional Programming
« Reply #5 on: April 24, 2005, 10:01:29 AM »
I've been thinking about this for a while now and I think I now understand your way of using exceptions and it is ok. However, I still prefer my "own" method :)

On a failed connection the connect() function should return a error code(or status code as Herb Sutter calls it) describing what failed. I'm going to quote Andrew Hunt and David Thomas here:

Quote

 "We believe that exceptions should rarely be used as part of a program's normal flow; exceptions should be reserved for unexpected events. Assume that an uncaught exception will terminate your program and ask yourself, "Will this code still run if I remove all the exception handlers?" If the answer is "no", then maybe exceptions are being used in nonexceptional circumstances." (The Pragmatic Programmer)


This basically means that only if the connection should've succeeded an exception should be thrown. GNE cannot know if the connection is required to succeed and that's why it shouldn't throw an exception. I do see your concern for ignoring the failed connection status code but I think it is a programming mistake made by the user of GNE. When someone accidently ignores a status code he could check the logs for errors.

It could also be argued that connect() is documented to succeed in establishing a connection and on basis of that an exception could be thrown on failure (if function documentation states that it will always succeed in connecting it should throw exceptions on failures to do so). I don't think that anyone would agree with documenting connect() to always succeed, though.

About the ideal interface: I haven't got a clue what it could be like :D I'll leave creating that up to you...

Code: [Select]

ClientConnection connection;

try
{
connection.open( address, params );
connection.connect();
}
catch( std::exception& error )
{
// report error
}


StatusCode& result = connection.waitForConnect();

// No errors while connecting but it's unsure if the connection was established correctly

// If it's fatal not to be connected and the application cannot function correctly anymore
// it can throw an exception. If the application ignores the result in this case it is a
// programming mistake made by the one who made the application.
if( result != Success)
throw std::exception( "Error connecting to server" );

// On the other hand it might work perfectly fine with a failed connection
if( result != Success )
goBackToMainMenu();

// or
if( result != Success )
std::cout << "Error connecting to server (" << result.toString() << "). Retry (y/n)? " << std::endl;
...

Sure, the above result handling code could be implemented using exception handling, but then the caught exceptions wouldn't really be exceptions, would they?

Gillius

  • Administrator
  • User
  • *****
  • Posts: 147
    • http://www.gillius.org/
Exceptional Programming
« Reply #6 on: April 24, 2005, 01:10:23 PM »
I suppose the reason why I consider it suitable for an exception is because if a client starts a connection, they are wanting to connect.  Failure to connect is always an error condition in clients that I've seen -- when a connection fails they usually abort the attempt and either retry completely from the beginning or go back to the previous program state (a "rollback" if you will).

I think you probably value semantics more than I value convience in some cases when using exceptions.  The main reason why I prefer exceptions is mostly for error consolidation.  Of course a programmer can forget to catch exceptions or forget to check error codes, leading to a buggy program.  However, the programmer only needs to add one catch block to catch all exceptions in a huge block of code while with status code checking the programmer has to inject decisions at every function call.  This provides a bigger benefit to simple programs like the GNE example who are "single-tasked" and just want to abort with a message when something goes wrong.

IMHO, this has a few disadvantages:
[list=1]
  • The code is less readable.  Either the actual function calls are wrapped in an "if" statement or on the right side of a assignment with variable declarations above and decisions immediately below.  It is likely that error handling code will have greater size than the "positive" branch, which masks the true intention of the code.
  • Many times, the cleanup operations are the same for a block of errors, and this leads to code repetitions.  C programmers typically use goto in this case to go to a common cleanup.  This disadvantage is lesser than the previous because most of this can be mitigated through RAII.
  • [/list:o]

    I think we can agree that both methods are equivalent as you can convert an status code to an exception, and convert an exception to a status code.  Switch blocks and catch blocks are also the two equivalent styles.  One could argue that you can pass back more information with an exception, but you could do that if you made status codes be an object type rather than a POD type like int.

    Ultimately though, it is a matter of what the users want.  I might try to see if I could get some more opinions.  My friends at work prefer the exception approach as that is how it is done in Java and .NET, and we use Java and .NET at work.
Gillius
Gillius's Programming http://www.gillius.org/