Good uses of goto

I’m back to programming, rather than theologizing, for the next few weeks. Now I know I haven’t done much for a while, and so when today I wrote a bunch of subroutines which had more than the usual number of “goto” statements in them, I wondered whether I’d just lost my touch. But I don’t think so. I think in both cases, this is a rare “good” use of goto.

The first one is quite straightforward. In Knuth’s famous paper on useful uses of goto in structured programming, he suggests that error handling is one area where goto statements may be useful.

Here’s the code I started with:

my $pass = $mm->param("password1");
if (!$pass) {
    $mm->message("Please enter your new password");
    return $mm->respond("plugins/broadband/password_change");
}

if ($pass ne $mm->param("password2")) {
    $mm->message("Passwords don't match");
    return $mm->respond("plugins/broadband/password_change");
}

if (!$self->_validate_password($mm, $pass)) { 
    # _validate_password supplies an error message so we don't need to
    return $mm->respond("plugins/broadband/password_change");
}

Sometimes we can forget that programming is an artistic discipline and not a hard science; There’s More Than One Way To Do It, and although we’d like to look for black-and-white rules to tell us which way to choose, it’s a question of style. Programming style is all about balance, and this is an example of where you have to balance one rule (structured programming) against another (Don’t Repeat Yourself). I made the stylistic decision to favour DRY, and replaced the code with something that looked like this:

my $pass = $mm->param("password1");
if (!$pass) {
    $mm->message("Please enter your new password"); goto fail;
}

if ($pass ne $mm->param("password2")) {
    $mm->message("Passwords don't match"); goto fail;
}

if (!$self->_validate_password($mm, $pass)) { goto fail; }

...
return;

fail: return $mm->respond("plugins/broadband/password_change");

Oh no, it’s a horrible goto statement! But it does make the code clearer and more maintainable - now if I need to change the name of the response template, I have to change one line, not three.

#london.pm was quick to point out another option, of course: exceptions. Perl doesn’t have an exception system, but you can fake them with eval and die. Or you can add one of a dozen CPAN modules which will give you some syntactic sugar … around faking exceptions with eval and die. It comes to the same thing, just with more code dependencies.

Rewriting the code with exceptions would look like this:

my $pass = $mm->param("password1");

eval { # You can read that as "try" if you like
    if (!$pass) { die "Please enter your new password";}  # Or "throw"
    if ($pass ne $mm->param("password2")) { die "Passwords don't match"; }
    $self->_validate_password($mm, $pass); 
    # We'll have to rewrite this subroutine to throw an exception too
};
if ($@) { # If you want to call that "catch" it doesn't bother me
    $mm->message($@);
    return $mm->respond("plugins/broadband/password_change");
}

I’m not a fan of these fake exceptions, because now you have to wrap the codeblock in some scaffolding and rewrite one subroutine, (and all the other code elsewhere which uses it) just for the sake of ideological purity, and (and this is the killer for me) you lose the ability to use die for what everything else in Perl uses die for, which is to signal a programming error.

Lots of work, little gain.

I’m quite happy with my current goto implementation, but here’s a third option, though, which is quite tempting:

my $fail = sub { 
    my $m = shift; $mm->message($m) if $m;
    return $mm->respond("plugins/broadband/password_change");
};

my $pass = $mm->param("password1");

return $fail->("Please enter your new password" unless $pass;
return $fail->("Passwords don't match") if $pass ne $mm->param("password2");
return $fail->() unless $self->_validate_password($mm, $pass); 

I hate changing my mind while I’m writing, but actually now that I’ve written that out, it’s a clear winner: it’s both structured and avoids too much repetition. (Apart from return $fail-> but you can’t have everything.) I use this pattern elsewhere in my code, so it’s not too much of a stretch to use it here as well.

The other use of goto in today’s code is a bit more of a contentious one. I’m writing a web application and one of the methods is what we used to call a “wizard” - it guides the user through a linear progression of steps: step one, get the user to choose between a variety of options; step two, confirm the user’s intention; step three, do the action and present the results. There’s a lot of shared state between the three steps. In an ideal world, we’d do this in a continuation passing style, callcc’ing the user to get an action from her and then carrying on where we left off, but we’re seriously not in an ideal world here. So the usual way to do it is as a state machine:

if ($state eq "step1") {
    # Present options to the user
} elsif ($state eq "step2") {
    # Summarize options and give a ticky box to confirm
} elsif ($state eq "step3") {
    # Do the thing and report results
}

This is familiar and makes sense, but it just feels awkward to me: the if/elsif structure is obscuring the linearity of the operation. It looks like the program should be alternating between a list of things, but it isn’t - it’s going directly from one to the next. To get exactly the same flow of control but to make the flow of the program read like the flow of operation, I’m planning to rewrite it like this:

goto $state; # OH MY GOODNESS COMPUTED GOTO HAVE YOU LOST YOUR MIND?

step1:
    # Present options to the user

step2:
    # Summarize options and give a ticky box to confirm

step3:
    # Do the thing and report results

The first thing to note is that if and goto are the same thing. In fact, all control flow is just syntactic sugar around goto. But for me - and this is a matter of style, of course - the second way of expressing this code is a lot more readable than the first, and matches more closely what is going on in the user’s experience.

Do you agree? Is there a better way to do it?


neverclickonthislink