The Artima Developer Community
Sponsored Link

Weblogs Forum
How Do You Structure Flow in the Presence of Potential Errors?

18 replies. Most recent reply: Aug 23, 2007 7:09 AM by Frank Silbermann

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a flat view of this topic  Flat View
Previous Topic   Next Topic
Threaded View: This topic has 18 replies on 1 page
Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

How Do You Structure Flow in the Presence of Potential Errors? (View in Weblogs) Posted: Jul 27, 2007 1:16 PM
Reply to this message Reply
Summary
In my previous post I brought up the topic of code formatting. This time I'd like to bump it up a level of abstraction to the structure of the code itself. To what extent do you think teams should establish policies for code structure, and what do you think is the best way to structure flow in the presence of potential errors?

In addition to fulfilling its primary responsibility, a method often has to handle potential error conditions. You can organize the control flow of such a method in many ways. I once had a manager who told me he preferred to see the path with no errors first, followed by error handling code. This was back when I was programming in C, but the style I adopted then still echoes in my code today. For example, I might structure a method that handles an HTTP POST from a form like this:

if (userIsSignedIn) {
    // The user is indeed signed in, so keep going...
    if (finished) {
        // They pressed finished, so keep going...
        if (!formErrors) {
            // Actually do what the user was trying to
            // accomplish with this form.
        }
        else {
            // handle form errors
        }
    }
    else {
        // they pressed cancel, so abort
    }
}
else {
    // handle user not signed in problem
}

My C code back in those days, therefore, often had the shape of a big right angle bracket, with several cascading ifs going in, then the "success" code, followed by elses handling all the potential errors the method might encounter.

When I was discussing this issue with a fellow programmer (not a manager) back in those days, he said that he prefered a method's code to resemble more of a pipe character (|) than an angle bracket (>). He wanted the code to be flat against the left hand side. Thus he'd write the above code like this:

if (!userSignedIn) {
    // handle user not signed in problem
    return;
}

if (!finished) {
    // they pressed cancel, so abort
    return;
}

if (formErrors) {
    // handle form errors
    return;
}

// Actually do what the user was trying to
// accomplish with this form.

The benefit with this approach is that my colleague avoided the cascading indentation, but at the cost of multiple returns. I had heard some programmers complain that having multiple returns inside a method makes it harder to understand. I myself didn't find such methods harder to understand, but now that I've started using Scala for a few things, I've begun to see methods more as expressions. This mindset implies that a method contains just one expression that resolves to a value, which is returned.

But even if you take the single return approach in a method, you still may prefer to handle the errors first, because they are often shorter than the "success" code. That might look like this:

if (!userIsSignedIn) {
    // handle user not signed in problem
}
else if (!finished) {
    // they pressed cancel, so abort
}
else if (formErrors) {
    // handle form errors
}
else {
    // Actually do what the user was trying to
    // accomplish with this form.
}

The benefit with this approach is that you can avoid both multiple returns from the method and the cascading indentation, but the errors are on top. Some may actually find this easier to read, but my old manager at least wouldn't like having to scan all the way to the bottom to find the code that we hope will be executing most of the time.

Lastly, one other possibility is to use exceptions to structure the flow of the method. For example:

try {
    User user = getSignedInUser();

    ensureFinishButtonPressed();

    Map formFields = getFormFields();

    // Actually do what the user was trying to
    // accomplish with this form.
}
catch (UserNotSignedInException unsie) {
    // handle user not signed in problem
}
catch (UserPressedCancelException upce) {
    // they pressed cancel, so abort
}
catch (FormErrorsException fee) {
    // handle form errors
}

private User getSignedInUser() {
    if (!userIsSignedIn) {
        throw new UserNotSignedInException();
    }
    // else return the signed in User
}

private void ensureFisishButtonPressed() {
    if (!finished) {
        throw new UserPressedCancelException();
    }
}

private Map getFormFields() {

    if (formErrors) {
        throw new FormErrorsException();
    }
    // populate a map with form fields and return
}

The previous example is very contrived, but should demonstrate the potential structure. When I first read about exceptions in Java, I noticed that it allowed me to put error handling code after the success code. It flattened the success code and made it easier to read. But I didn't adopt exceptions for this kind of situation, in which I'm really trying to implement one method. (Notice that the methods throwing exceptions are all private, created solely to help me implement the HTTP POST handler method.)

My question today has two parts. First, which of these approaches (or perhaps you have a different approach to suggest) would you recommend for structuring the flow of methods that must handle potential errors? Second, in my experience, each individual programmer on a project would use his or her own preferred style of structuring such flow—in other words, how flow was structured was decided by personal coding style. Most people who participated in the discussion of my previous blog post seemed to feel that establishing a code style for a project was a good idea. But how far should that style policy go? Should it only deal with formatting issues such as curly brace placement? Or should it actually address coding structure issues, such as the one presented in this blog post? Please post your opinions in the discussion forum.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 27, 2007 5:01 PM
Reply to this message Reply
Posted by: Raoul Duke    Posts: 127 / Nickname: raoulduke / Registered: Apr, 2006
(As I'm sure has been noted several times in various Artima forums already) D's scope-exit stuff seems like a nicer syntax than the try/catch/finally cruft: http://www.digitalmars.com/d/exception-safe.html


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 29, 2007 2:55 AM
Reply to this message Reply
Posted by: Tobias Mattsson    Posts: 6 / Nickname: tobiasm / Registered: Jan, 2007
I prefer approach two, testing for error conditions early. Mostly because it easier to read and therefor easier to maintain and reason about.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 29, 2007 6:54 AM
Reply to this message Reply
Posted by: Erik Engbrecht    Posts: 210 / Nickname: eengbrec / Registered: Apr, 2006
I would use exceptions in cases where I may or may not want to handle the error locally. It would be common to have a global method for redirecting users to a login page. There could also be generic "redisplay the form with errors" logic in the application to respond to the form error, so that could be an exception but is a little more iffy.

I'd use if/else for distinguishing "finish" from "cancel."

So I guess it would look something like this:

    User user = getSignedInUser();  // could throw exception
    if (finished) {
        Map<String, String> fields = getFormFields(); // could throw exception
        // ...process the form....
    } else {
        // ...abort handling the form
    }


And the handlers for the exceptions would be at a higher level.


Use Continuations and Exceptions Posted: Jul 29, 2007 9:53 PM
Reply to this message Reply
Posted by: Todd Blanchard    Posts: 316 / Nickname: tblanchard / Registered: May, 2003
In seaside, you simply test and if the test fails, you suspend calculation and call another page to resolve the error. Maybe they can't resolve the error, then you won't come back here anyhow.

Non-local returns, yes, but very simple code for the developer that looks like the actual business process rather than http protocol.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 29, 2007 11:45 PM
Reply to this message Reply
Posted by: Mike Ivanov    Posts: 23 / Nickname: mikeivanov / Registered: Jul, 2007
Look how much complexity the exception-based method adds, especially comparing to the pipeline (2). Exceptions work well in simple situations like this, but in real life the error processing code has actually to be mixed with the main flow for the sake of robustness. This is achievable with exceptions chained as in (2), but again, with significant clarity loss. Resume: exceptions work well if correctness is the only thing you care for.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 29, 2007 11:49 PM
Reply to this message Reply
Posted by: Mike Ivanov    Posts: 23 / Nickname: mikeivanov / Registered: Jul, 2007
I meant you not always want to return in (2). Sometimes what you need is just a flag set and one more test.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 6:01 AM
Reply to this message Reply
Posted by: Nemanja Trifunovic    Posts: 172 / Nickname: ntrif / Registered: Jun, 2004
If you add the "cleanup code" to your samples (releasing resources such as files and db connections) it would show why the "pipe" version is bad.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 12:16 PM
Reply to this message Reply
Posted by: Mike Ivanov    Posts: 23 / Nickname: mikeivanov / Registered: Jul, 2007
Nemanja, one big try-catch will not help either. The proper place for the cleanup code is inside 'finally' blocks.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 1:01 PM
Reply to this message Reply
Posted by: Nemanja Trifunovic    Posts: 172 / Nickname: ntrif / Registered: Jun, 2004
> Nemanja, one big try-catch will not help either.

Never said it would.

> The
> proper place for the cleanup code is inside 'finally'
> blocks.

This is really language dependent (I mostly use C++ with its deterministic destructors) but IIRC Java's finally works only if you have a try block before it, no?


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 8:00 AM
Reply to this message Reply
Posted by: Daniel Jimenez    Posts: 40 / Nickname: djimenez / Registered: Dec, 2004
Decorator seems like a good answer here, but oh my, the cost. Interfaces must be created, arguments passed to the methods must be sufficiently generic to handle each part of the processing yet specific enough to actually solve the problem, the business process is split across methods, exceptions must be defined and handlers inserted higher up the call stack. I like it, but I hate it more.

interface Handler {
  void handle(Request, Response) throws ???Exception(s?);
}
 
LoginHandler implements Handler {
  public void handle(Request, Response) throws ... {
    // notice how this has the if/else problem as well, just smaller
    if (request.user.loggedIn) request.add(request.user.asLoggedIn());
    else throw ...
 
    _decorated.handle(request, response);
  }
}
 
FormHandler implements Handler {
  public void handle(Request, Response) throws ... {
    if (valid(request.data)) request.add(request.data.asBusinessObjects());
    else throw ...
 
    _decorated.handle(request, response);
  }
}
 
BusinessHandler implements Handler {
  public void handle(Request, Response) throws ... {
    assert request.user.loggedIn;
    assert FormHandler.valid(request.data); // yuck!
 
    // do work
  }
}


I really dislike how the method arguments are request and response, not LoggedInUser, ValidFormData, etc. Could they act as a Decorator without requiring the same interface?

I also dislike how many classes have to be created just to handle a simple case. There may be advantage to the LoginHandler being reused, but that's lessened if the "Decorator"-ness uses different interfaces. Similarly form validation, with similar costs.

I do like that the work of performing the business task is separated from the error-checking code, but I feel the asserts represent duplication instead of just communication.

I write and maintain code with these issues frequently, as do many of us. I've tried this "decorator" scheme to minimal gain, tried the big-right-angle-bracket, tried the if-not-return guard conditions. Do the cutting-edge functional languages have an answer to this, um, messiness? There has to be a way better type systems can improve this, right? Like http://blog.moertel.com/articles/2006/10/18/a-type-based-solution-to-the-strings-problem ... but how could we port this back into languages without such expressive type systems?


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 7:40 PM
Reply to this message Reply
Posted by: Rhys Goldstein    Posts: 2 / Nickname: rhys / Registered: Jul, 2007
I'll start by commenting on the first three techniques, in reverse order...


Technique #3: Single If-Else Statement

Of the four techniques demonstrated for Bill Venner's sample problem, it's tempting to say the third is the best. That's the one written with a single if-else structure, avoiding both jump statements and exceptions. But as I see it, a team would get themselves into trouble by establishing technique #3 as a policy. It solves the sample problem well, but is inappropriate in more complex cases.

Consider, for example, the problem one encounters when loading data from a file. One must first check if the file exists, then later check whether the file's text is in the correct format. But in between these two checks, one must open the file and read its contents. Because of this "in between" code, the problem is awkward to formulate with a single if-else statement.


Technique #2: Multiple Return Statements

This technique handles the "in between" code I referred to above. Below I've copied Bill Venner's second example, but inserted two "in between" statements and turned the conditions into function calls. Let's assume that "do_something()" only succeeds if the user is signed in, and must be executed before "finished()" is evaluated. Similarly, "do_something_else()" must come before "formErrors()".

if (!userSignedIn()) {
// handle user not signed in problem
return;
}

do_something();

if (!finished()) {
// they pressed cancel, so abort
return;
}

do_something_else();

if (formErrors()) {
// handle form errors
return;
}

// Actually do what the user was trying to
// accomplish with this form.

We cannot easily transform this code into a single if-else structure, but the return statements give us an adequate solution. Does that mean a team should adopt technique #2 as policy instead? Again, I think not. As Nemanja Trifunovic pointed out, return statements are inappropriate for problems involving clean-up code.

(Interestingly, goto statements are more convenient for clean-up code, and appear to be the policy adopted by the Linux Kernel Project. See http://lxr.linux.no/source/Documentation/CodingStyle#L358)


Technique #1: Nested If-Else Statements

Personally, I've come to dislike all jump statements (goto, early return, break, continue). Although they're very convenient, I feel they limit a programmer's options when deciding which sections of code to extract into smaller functions. I also don't like exceptions much, so here I'll consider the "nested if-else" technique demonstrated in Bill Venner's first example.

Technique #1 is both simple and general. But if it were adopted as a policy, it would lead the design of deeply-nested structures. Certain error-handling routines would inevitably be located far below the conditions that trigger them. In such cases, it's hard to figure out which "else's" correspond to which "if's".


Alternative: No Policy

Consider the following solution to the sample problem:

private void process_form() {
if (!formErrors) {
// Actually do what the user was trying to
// accomplish with this form.
}
else {
// handle form errors
}
}

[...]

boolean ok = false;
if (!userIsSignedIn) {
// handle user not signed in problem
}
else {
if (!finished) {
// they pressed cancel, so abort
}
else {
ok = true;
}
}
if (ok) process_form();

Part of the problem has been extracted into a function called "process_form". This function might be useful if it can be re-used in a context in which the user's state is irrelevant. Also, a boolean named "ok" has been introduced, which might be useful in subsequent code. The above is not the best solution in general. But in a particular situation, it may be better than the code obtained through any of the three techniques.

Perhaps coding policies are helpful for simple, well-defined problems. But as a category, I think "error-handling" is too broad to select a single technique for all situations. Teams should therefore allow individual programmers to formulate their code in the way that best suits the situation at hand.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 31, 2007 6:41 AM
Reply to this message Reply
Posted by: Achilleas Margaritis    Posts: 674 / Nickname: achilleas / Registered: Feb, 2005
I prefer the multiple return/throw statements in C, checking for errors before anything is executed. It makes the code cleaner and safer in many times.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 31, 2007 6:56 AM
Reply to this message Reply
Posted by: Erik Engbrecht    Posts: 210 / Nickname: eengbrec / Registered: Apr, 2006
> I prefer the multiple return/throw statements in C,
> checking for errors before anything is executed. It makes
> the code cleaner and safer in many times.

Me too. Multiple returns are only unclear when they are buried in code. Short if/then/return statements at the beginning of a method are harmless as long as they are clear.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Aug 1, 2007 9:53 AM
Reply to this message Reply
Posted by: Bill Venners    Posts: 2284 / Nickname: bv / Registered: Jan, 2002
> I prefer the multiple return/throw statements in C,
> checking for errors before anything is executed. It makes
> the code cleaner and safer in many times.
>
When I wrote this blog last week I was surprised that option 3 came out like a pipe, even though it has no early returns. I think the pipe shape is better than the angle bracket. It is easier to read. But since I can do it without early returns, I'd prefer doing it that way. I didn't get enough responses here to really judge what people in general actually prefer, but many of the responses that came back preferred pipe over angle bracket shape.

I'm curious if those of you who said you like the return statement were to chose between option 1 and option 3, would you choose 1 over 3? If so, why? Because I would lean towards 3.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Aug 1, 2007 4:04 PM
Reply to this message Reply
Posted by: Rhys Goldstein    Posts: 2 / Nickname: rhys / Registered: Jul, 2007
> I'm curious if those of you who said you like
> the return statement were to chose between
> option 1 and option 3, would you choose 1
> over 3? If so, why? Because I would lean
> towards 3.

The problem with 3 is that a single if-else structure does not accomodate a situation like the following:

if (!file.exists()) {
print("Error: file does not exist");
return;
}
text = file.read(); // "in-between" statement
if (text == null) {
print("Error: failed to read file");
return;
}
lines = text.split(); // "in-between" statement
if (lines.size() == 0) {
print("Error: no header found");
return;
}
return parser.parse(lines);

The difference between this example and the original is the so-called "in-between" statements. If you try, I'm sure you'll find that forcing this example into an option 3 structure will result in a big mess. In practice, I find these "in-between" statements very common, so I would not adopt option 3 as a policy for code with potential errors.

Option 1 is a different story. It accomodates the above, and just about any related problem you can come up with. But I also prefer the pipe over the angle bracket shape, so I don't actually like option 1. And of course option 2 has the returns (which many dislike like for various reasons) and option 4 has the exceptions (which don't feel appropriate in all situations).

This problem has interested me for many years, since most of the ugly code I've read/written seems to fall into the "potential error" category. My advice would be to use option 1, but aggressively flatten out the "angle" by introducing booleans and extracting inner structures into smaller methods.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 31, 2007 4:50 PM
Reply to this message Reply
Posted by: robert young    Posts: 361 / Nickname: funbunny / Registered: Sep, 2003
Early on in the history of java, it was understood that exceptions should be thrown when a condition occurred which the code could not negotiate. That exceptions should not be used for flow control.

Early on in the history of languages, it was understood that functions should have one exit point/return. This was not for so much for machine efficiency, but logical consistency.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Aug 1, 2007 9:59 AM
Reply to this message Reply
Posted by: Bill Venners    Posts: 2284 / Nickname: bv / Registered: Jan, 2002
Hi Robert,

> Early on in the history of java, it was understood that
> exceptions should be thrown when a condition occurred
> which the code could not negotiate. That exceptions
> should not be used for flow control.
>
Well technically that's what was going on in this example. The getSignedInUser method doesn't know how to handle the error condition locally, so it throws an exception. But the trouble here is that the intent of factoring functionality out into that getSignedInUser method in the first place was so the programmer could use exceptions for that error particular condition in the handler method. In Java that doesn't feel like a good use of exceptions to me.

> Early on in the history of languages, it was understood
> that functions should have one exit point/return. This
> was not for so much for machine efficiency, but logical
> consistency.
>
I'm not sure this was ever the consensus. I heard people have strong opinions in either direction. The people who liked having many explicit returns tended to do so because they felt it made the code more readable. One reason I heard against it was that it was easier to put a breakpoint right at the return of a method, if there's just one return point. I usually have tried to have just one return, but now and then would stray if it was awkward to do so. Now that I'm starting to use Scala, it has made me want to make each method an expression, whose result is automatically returned. So there's no return statement anywhere in the method. I suspect this will push me into factoring larger methods into multiple smaller ones more than I did in the past, and I suspect that will make my code better.


Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Aug 23, 2007 7:09 AM
Reply to this message Reply
Posted by: Frank Silbermann    Posts: 40 / Nickname: fsilber / Registered: Mar, 2006
The first approach -- big right angle bracket -- is bad because it is difficult for the reader to match up conditions with responses without a ruler.

The second approach (test for error, handle it and return or continue) is easiest to read and understand despite the multiple exits. That would be my first choice.

The third approach (if error then handle it, else do the rest) is semantically the same as the previous approach; it merely adds syntactic clutter. It's only advantage is that if you later have some logic you must append to every single case, you have a place to put it. But YAGNI. This would be my second choice.

The fourth approach (try/catch) is inappropriate use of the language, as others have pointed out. The business logic requires first checking for user error and handling it if necessary. Using exceptions in this case obscures this logic.

How do I decide when, if ever, to use the exception mechanism? I want the text of the code to highlight the _business_ logic; I consider errors caused by fallible users to be part of that business logic which I want highlighted. Errors caused by faulty computer hardware (e.g. databases being down), in contrast, are not part of the _business_ logic and therefore _should_ be buried in a catch block.

If you are concerned that large blocks of error-handling code unduly delay presentation of usual non-error case, then export your error-handling code into their own methods which you call from the main routine.


Topic: How Do You Structure Flow in the Presence of Potential Errors? Previous Topic   Next Topic Topic: Last Week's Flex-AIR Jam

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use