Monday, June 14, 2004

Return-codes vs. Exceptions, part 318



User interface design for ProgrammersIn the never-ending saga/debate/raging conflict, PHP Everywhere's John Lim weighs in with an interesting take:


I totally agree with Doug with regards to real-time software (you want to handle the error as soon as possible with an error code).
However that's not the way I would want to write financial code nowadays. With the relational databases we now use, I'm more likely to throw an exception and perform a database rollback in the exception handler.
In general, technologies that provide timely automated resource cleanup (such as database transactions) make throwing exceptions practical for industrial strength apps.


The question I would ask: how do we draw the line between real-time/mission-critical code and "other" code? How do we gauge whether exceptions or return-codes should be used? Do we simply ask a battery of questions such as:


Please fill in the following:
1) Code is real-time and/or mission-critical: ___ Yes ___ No
2) Code runs in a JVM or environment that provides automated resource cleanup: ___ Yes ___ No
3) Code needs to provide tunable logging for post facto analysis: ___ Yes ___ No
4) Code executes business-rules: ___ Yes ___ No
5) Code is a front-end for a financial/insurance application: ___ Yes ___ No
(continued on page 12)


I contend that if the code is production-ready (in other words, it's not a test harness or utility program), it probably is mission-critical and should provide a tunable logging environment such that faults can be analyzed after the fact. And this goes for user-interface code, not just low-level system services, web services and the like.

Think of a thick-client application: wouldn't it be great to watch the flow of user activity... to find out which screens seem to confuse them, which screens appear intuitive, which screens are heavily used and those that are ignored? Tunable logging, which I consider part and parcel of a proper return-code structure, is a key element of reliable code (at least, in my book).

Here's my updated example:



do { try {

if ((rc = tableCredits.open()) != OK) {
TunableLog("Credits open failed...");
break;
}
bUnwindTableCreditsOpen = TRUE;
TunableLog("Credits open succeeded...", 5);

if ((rc = tableCredits.lock()) != OK) {
TunableLog("Credits lock failed...");
break;
}
bUnwindTableCreditsLock = TRUE;
TunableLog("Credits lock succeeded...", 5);

if ((rc = tableDebits.open()) != OK) {
TunableLog("Debits open failed...");
break;
}
bUnwindTableDebitsOpen = TRUE;
TunableLog("Debits open succeeded...", 5);

if ((rc = tableDebits.lock()) != OK) {
TunableLog("Debits lock failed...");
break;
}
bUnwindowTableDebitsLock = TRUE;
TunableLog("Debits lock succeeded...", 5);

if ((rc = ::IntegralTransaction(tableCredits, tableDebits, curAmount)) != 0) {
TunableLog("Integral transaction failed...");
break;
}
TunableLog("Integral transaction succeeded...", 3);

} catch (...) {
// catch miscellaneous exceptions here
} } while (0);

if (bUnwindTableDebitsUnlock) {
tableDebits.unlock();
}
if (bUnwindTableDebitsOpen) {
tableDebits.close();
}
if (bUnwindTableCreditsLock) {
tableCredits.unlock();
}
if (bUnwindTableCreditsOpen) {
tableCredits.close();
}
return (rc);


Note the tunable logging, which I believe to be one of the keys to writing bullet-proof code. The TunableLog method/macro/whatever allows an external verbosity level to control which events are recorded. More on this later.

No comments: