The Artima Developer Community
Sponsored Link

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

18 replies on 2 pages. 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 threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 18 replies on 2 pages [ 1 2 | » ]
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?
Advertisement

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.


Raoul Duke

Posts: 127
Nickname: raoulduke
Registered: Apr, 2006

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 27, 2007 5:01 PM
Reply to this message Reply
(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

Tobias Mattsson

Posts: 6
Nickname: tobiasm
Registered: Jan, 2007

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

Erik Engbrecht

Posts: 210
Nickname: eengbrec
Registered: Apr, 2006

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 29, 2007 6:54 AM
Reply to this message Reply
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.

Todd Blanchard

Posts: 316
Nickname: tblanchard
Registered: May, 2003

Use Continuations and Exceptions Posted: Jul 29, 2007 9:53 PM
Reply to this message Reply
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.

Mike Ivanov

Posts: 23
Nickname: mikeivanov
Registered: Jul, 2007

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 29, 2007 11:45 PM
Reply to this message Reply
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.

Mike Ivanov

Posts: 23
Nickname: mikeivanov
Registered: Jul, 2007

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

Nemanja Trifunovic

Posts: 172
Nickname: ntrif
Registered: Jun, 2004

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 6:01 AM
Reply to this message Reply
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.

Daniel Jimenez

Posts: 40
Nickname: djimenez
Registered: Dec, 2004

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 8:00 AM
Reply to this message Reply
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?

Mike Ivanov

Posts: 23
Nickname: mikeivanov
Registered: Jul, 2007

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

Nemanja Trifunovic

Posts: 172
Nickname: ntrif
Registered: Jun, 2004

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 1:01 PM
Reply to this message Reply
> 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?

Rhys Goldstein

Posts: 2
Nickname: rhys
Registered: Jul, 2007

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 30, 2007 7:40 PM
Reply to this message Reply
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.

Achilleas Margaritis

Posts: 674
Nickname: achilleas
Registered: Feb, 2005

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 31, 2007 6:41 AM
Reply to this message Reply
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.

Erik Engbrecht

Posts: 210
Nickname: eengbrec
Registered: Apr, 2006

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 31, 2007 6:56 AM
Reply to this message Reply
> 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.

robert young

Posts: 361
Nickname: funbunny
Registered: Sep, 2003

Re: How Do You Structure Flow in the Presence of Potential Errors? Posted: Jul 31, 2007 4:50 PM
Reply to this message Reply
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.

Flat View: This topic has 18 replies on 2 pages [ 1  2 | » ]
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