If statements versus other decision trees

I'm a novice. The reading that I've done so far, seems to imply that multiples of nested "if" statements are undesirable. People seem to speak of them, sometimes, with contempt. However, there seem to be problems that can't be solved any other way.

My question is this:
Is there any other way, besides nested "if" statements, to solve the following analog problem?

There is a wall.
Your enemy might be on the other side of the wall; at the moment, you don't know.
There is a set of stairs.
The only way to determine if your enemy is on the other side of the wall is to clime the stairs in order to see over the wall, so . . .
Take the first step.
If you don't see your enemy and an arrow does not graze your head.
Take the second step.
If you don't see your enemy and an arrow does not graze your head.
Take the third step. . . and so on . . .
If at any point you see your enemy, or an arrow grazes your head, or you run out of stairs, exit the stairway.
For whatever reason, you can't just loop because each stair is different from the previous, so maybe you're stepping on a box, then a bench, then a barrel . . .yadda yadda.

You don't have to nest, you could just serially test.

if box_resulted_in_arrow {
    return;
}
if bench_resulted_in_arrow {
    return;
}
// ...

In Rust, these early returns are often created more ergonomically via the ? (aka Try) operator.

Contrived example.

3 Likes

Ok, I think I see. The "TRUE" exits the process "prematurely", as it where, whereas when nesting, the "TRUE" keeps you inside the nest. This seems similar to the GoTo from VBA, or GO TO from Fortran, but more restrictive in terms of where you end up after the "TRUE". ?

This distaste comes from multiple factors.

  1. Understanding the logic of a complex nested if requires keeping track of which conditions are true in each branch of the code. Writing the code in another way can reduce the amount of information that needs to be tracked, by turning the code into a series of steps that are like “okay we've taken care of the simple case (or the error case), now we can proceed with handling the rest of the problem”; that's what @quinedot's example does. The human mind does better at comprehending this kind of thing than deep nesting.

  2. Even disregarding readability considerations which may be subjective, a classic beginner mistake is to write a complex nested if when there is a better solution entirely, such as a match, an algorithm that can handle all cases without branching at all, or even a data structure of some sort.

  3. Nested blocks of any kind increase indentation, thus leaving less horizontal space for the remaining code.

Thus, many experienced programmers will look at a nested if and immediately feel that the code could surely be improved.

Of course, there are cases when a nested if cannot be avoided, or is the clearest thing:

if compute_condition_a() {
    if compute_condition_b() {
        handle_ab();
    } else {
        handle_a();
    }
} else {
    if compute_condition_c() {
        handle_c();
    } else {
        none_of_the_above();
    }
}

This code can't be entirely un-nested without changing what it does or hiding the similarity between the four final cases.

The "TRUE" exits the process "prematurely", as it where, whereas when nesting, the "TRUE" keeps you inside the nest.

Just so. By staying out of nesting, the reader sees a linear sequence of steps just as you described the procedure, rather than one with increasing nesting.

This seems similar to the GoTo from VBA, or GO TO from Fortran, but more restrictive in terms of where you end up after the "TRUE". ?

Yes. You might find it interesting to read about the history of structured programming — originally, all control flow was written with conditional “goto”s, and this proved to be difficult to comprehend when they were written in ad-hoc ways, so patterns of usage were developed, and then built into new programming languages, creating the ubiquitous if and while/for/loop structures we have today. There was even a period where some style guides felt that early returns (return; in an if) were too goto-like and should be avoided entirely (so that a function always has execution continue to the end of its body), but that's a rare opinion today.

7 Likes

FYI, the practice of checking preconditions and early returning has a name: "Guard". It's so common we generally don't even think about it as something that could be named.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.