Disclaimer

The content of this blog is my personal opinion only. Although I am an employee - currently of Nvidia, in the past of other companies such as Iagination Technologies, MIPS, Intellectual Ventures, Intel, AMD, Motorola, and Gould - I reveal this only so that the reader may account for any possible bias I may have towards my employer's products. The statements I make here in no way represent my employer's position, nor am I authorized to speak on behalf of my employer. In fact, this posting may not even represent my personal opinion, since occasionally I play devil's advocate.

See http://docs.google.com/View?id=dcxddbtr_23cg5thdfj for photo credits.

Monday, July 27, 2009

Error Handling

I've been thinking a lot about the examples Andrei Alexandrescu used in his talks about the D programming language.

Briefly, Andrei pointed out how the "Hello World", simplest possible program examples, in nearly all popular programming language books, are not correct, or at leastr are not correct if an error occurs. They do not check for the return value of functions like printf, or they do not arrange for the OS to see an appropriate error code if the program fails.

Andrei argues that a programming language should make it as easy as possible to write correct code, complete with error handling.

The D programming language has constructs such as scope(exit/success/failure) and the now ubiquitous try/catch to make resource management in the presence of exceptions easier. But overall, the D strategy is to have errors be reported by exceptions. A lazy programmer, writing the simplest possible program, may assume success; the exceptions will get thrown by library routines on an error, and the default excedption handler will propagate the error correctly into the OS's error exit codes, etc.

Comprehensive error detection should be accomplished with the simplest possible code. Without having to clutter the code with error handling, IF syscall_return_code ! 0 ... Comprehensive error handling still requires a higher level of expertise, but there D's new features may help.

---

I think this is all good.

However, I think that scattering try/catch all over the place is quite ugly, leading to cluttered code. Yes, RAII and scope(exit) will help. But occasionally they are not the right thing.

I've written libraries that use exception throwing as thweir error reporting strategy. (I often throw string error messages, and stack up the error messages from most abstract to most detailed.)


I've written test suites for such libraries with exception based error reporting. They often look like long lists of test cases like the following:

expected_exception_caught = 0;
try {
test_function_1(a,b,c);
}
catch (.... ) {
assert("expected this exception");
expected_exception_caught = 1;
}
if( ! expected_exception_caught ) {
assert( ! "did not get expected exceptrion" );
}


I will often use macros to make this more compact. Or test suite lists, with an exception expected flag.




I have had reason to compare such code to code using traditional C style exit codes. When a function must return an ext code, it's API becomes clumsy, because it must arrange for the real return value to be returned some other way, typically via a pointer to a return area.

However, the code that exercises the error return code libraries often looks clearner, than the try/catch code.



I'd like the best of both worlds. Conceptually, return a tuple (error_code, return_value). (For that matter, multiple return values are nice.)

However, allow the programmer to omit the capture of the error_code return value. If not captured, throw an exception.

Also, possibly, to prevent the programmer from just capturing an erro_code return value, but doing nothing with it, require a captured error_code to be "consumed" - explictly markeed "I have handled this error."

Possible something like

{
(Error_Code error_code, char* string) = test_function_1(a,b,c);
assert( error_code && "error was expected" );
}






2 comments:

Unknown said...

Welcome to the beautiful world of D templates:

T AssumeException( ExceptionType, T, U... )( T function( U ) fn, U args ) {
bool result = true;
try {
fn( args );
} catch ( ExceptionType ) {
result = false;
}
return result;
}

Usage:

if ( AssumeException!( AssertError )( foo ) ) {
// Correct Exception thrown.
} else {
// Wrong Exception thrown.
}

Andy "Krazy" Glew said...

> Welcome to the beautiful world of D templates ...

Fair enough - so long as there are lambdas, or some other anonymous code blocks.

Allowing us to write

AssumeException!( AssertError )( {
// code block that is eval'ed as
// fn foo
});