Thursday, August 30, 2012

Musings

As I occasionally mention, I'm fond of writing my functions like this:
bool st = true;
st= st&&   InitSW ();
st= st&&   InitHW ();
st= st&&   MakeDinner (PASTA);
st= st&&   food.IsDelicious();

return st;

Nice and simple. However, sometimes you need to add informative error messages for the user's sake:
bool st = true;
st= st&&   InitSW ()           ||  Error ("Software error!");
st= st&&   InitHW ()           ||  Error ("Hardware error!");
st= st&&   MakeDinner (PASTA)  ||  Error ("Critical food error!");
st= st&&   food.IsDelicious()  ||  Error ("Ewww!"); 

if ( ! st)
    msg (GetError());
return st;

Unfortunately, not all languages were blessed with C++'s convenient lazy evaluation so it doesn't work exactly as expected and you need parenthesis. Also sometimes you're writing in PHP and you need some dollar signs:
$st = true;
$st= $st&&   (InitSW ()           ||  Error ("Software error!"));
$st= $st&&   (InitHW ()           ||  Error ("Hardware error!"));
$st= $st&&   (MakeDinner (PASTA)  ||  Error ("Critical food error!"));
$st= $st&&   (food.IsDelicious()  ||  Error ("Ewww!")); 

if ( ! $st)
    msg (GetError());
return $st;

OK, this mess is starting to get out of hand. There are too many elements in these lines which aren't part of what we're trying to do.

What I want is a way to write functions to accurately represent their, well, function, and often enough in the case of high level functions, what you want to do is a list of actions. But I want it to be readable and not too cluttered with overhead. You could say my requirements are:
  1. The main part of the function is a list, formatted as a table, of simple actions.
  2. All error handling is centralized, at the end of the function.
  3. Error messages are easy to find, but do not get in the way.
  4. The function has no more than 2 exit points.



The prettiest solution I've found so far is in Python, using assertions.
try :
    assert  InitSW (),             'Software error!'
    assert  InitHW (),             'Hardware error!'
    assert  MakeDinner (PASTA),    'Critical food error!'
    assert  food.IsDelicious(),    'Ewww!'

except AssertionError as err :
    msg ('An error has occurred!\n' + err)
    return False
Can code get any cleaner?
This has almost no overhead at all - no pointless assignments, functions, operators, nothing except the repeated assert. It can be made ever so slightly easier to understand by adding an Error function that gets an error string and returns it unchanged, but personally I think this is the most elegant way of doing this.

The practice of using exceptions for flow control is often frowned upon, but I think it's quite fitting in this case. After all, what the function is really supposed to do is the list of actions in the try block, and the only reason to stop following the list is an error, in which case it goes straight to the error handling.
It's also worth noting that the exception object can be anything, not just a string, which might allow some pretty powerful debug features.



In C++ I thought of using some classes with overloaded operators, or perhaps a macro, but I realize now that that's a result of the habit of using the method I started this post with. Having found the above Python solution, I realize that even in C++, where I started doing this, exceptions might be the right mechanism to use. You might need an auxiliary function, but you don't have to use assert, which eventually results in pretty clean code:
bool Error (string s) throw (string) {
    throw s;
}
...

try {
    InitSW ()            ||  Error("Software error!");
    InitHW ()            ||  Error("Hardware error!");
    MakeDinner (PASTA)   ||  Error("Critical food error!");
    food.IsDelicious ()  ||  Error("Ewww!");
} 
catch (string s) {
    msg (s);
    return false;
}
It's not Python, but still pretty elegant.



The above solution, with slight variations, could work in some other languages, including PHP.
But some people still frown upon the very idea of exceptions, so there are other options. This one looks pretty weird compared to common coding standards, but it's relatively clean and works in many languages:
st = true
&& (InitSW ()            ||  Error("Software error!"))
&& (InitHW ()            ||  Error("Hardware error!"))
&& (MakeDinner (PASTA)   ||  Error("Critical food error!"))
&& (food.IsDelicious ()  ||  Error("Ewww!"))
;
if ( ! st) {
    log (GetError());
    return false;
}
(Error saves the string somewhere handy, and returns false)
It's not as pretty, I think, but certainly functional.




I'd have liked to find an interesting solution in TCL for this problem, but I'd probably end up building a little parser and abusing upvar and uplevel, which is a lot of effort for a language almost nobody knows, so maybe another time.



This post was half improvised... I can't say I actually work like this. I did start using this kind of Python code last week and I expect to use the C++ version as well, but other than rudimentary testing I really can't testify about how useful it is. I've been using code that looks like the first snippet for years though, and it does make things so much easier on the eyes.

One interesting point about this method is that it forces you to plan your code's structure a little better. Assuming the project is complex enough, you're very likely to have 2 functions of this type calling one another, and then allowing both of them to display an error message is obviously a bad idea. Some people might consider that an addition of pointless effort, but personally I think that this extra thinking would lead to better code.

Anyway, that was just some musings about how to make my code better. I can only hope someone found it useful, or, better yet, that someone might have an even better way.

No comments:

Post a Comment