Q:

About the exclusiveness of the cases of an if block

I have a question about good coding practices. I understand the differences between doing an if-else if and multiple ifs (that is, when a condition is met in an if-else if, the rest of the checks are skipped). I've found a piece of code along these lines:
if (A == 5) {
    do_something();
} else if (B == 7) {
    do_something_else();
}

I understand that this code won't check B == 7 if A == 5. The code works, so that means that B is only 7, if A is not 5, but I think this is just waiting to break when the code changes. What I would do is:
if (A == 5) {
    do_something();
    return or continue or break;
}
if (B == 7) {
    do_something_else();
    return or continue or break;
}

My question is, when I have multiple exclusive cases that depend on different, exclusive variables, what's the best way to tackle the flow control? I have the impression that the first code (with else ifs) depends a lot on other pieces of code to work, and that changes in other areas might break it. The second one seems to be a bit clunky. A switch could be a third option, but I would need to create another structure to hold the case and the logic to assign its value, and I think that it would be a bit clunky and counter-intuitive.

A:

You asked about "exclusive" cases, but the issue with the conditions A == 5 and B == 7 is that they are not exclusive; they are independent.
For full generality you may need to test and handle all four cases:
if(A == 5) {
    if(B == 7) {
        /* case 1 */
    } else {
        /* case 2 */
    }
} else {
    if(B == 7) {
        /* case 3 */
    } else {
        /* case 4 */
    }
}

This is the notorious "bushy" if/else block.  It's notorious because it can almost immediately become nearly impossible for a reader to follow, especially if the cases are involved, or more levels are introduced.  (I think most style guides will tell you never to use an if/else tree that's 3 or more levels deep.  I'd certainly say that.)
I have occasionally used these two alternatives:
(1) Fully decouple the cases:
if(A == 5 && B == 7) {
    /* case 1 */
} else if(A == 5 && B != 7) {
    /* case 2 */
} else if(A != 5 && B == 7) {
    /* case 3 */
} else if(A != 5 && B != 7) {
    /* case 4 */
} else {
    /* can't happen */
}

The point here is to make it maximally clear to a later reader exactly which conditions go with cases 1, 2, 3, and 4.  For this reason, you might as well list the last, else if(A != 5 && B != 7) case explicitly (as I've shown), even though by that point it's basically an "else".
(2) Contrive a "two level" switch.  I can't say this is a common technique; it has a whiff of being "too clever", but it's robust and readable, in its way:
#define PAIR(b1, b2) (((b1) << 8) | (b2))

switch(PAIR(A == 5), (B == 7)) {
    case PAIR(TRUE, TRUE):
        /* case 1 */
        break;

    case PAIR(TRUE, FALSE):
        /* case 2 */
        break;

    case PAIR(FALSE, TRUE):
        /* case 3 */
        break;

    case PAIR(FALSE, FALSE):
        /* case 4 */
        break;
}

I wouldn't recommend this when the conditions are A == 5 and B == 7, because when you're down in the switch, it's not obvious what "TRUE" and "FALSE" mean, but sometimes, this sort of thing can read cleanly.  It's also cleanly amenable to 3 or more levels of nesting, unlike "bushy" if/else trees, which as I said are notoriously unreadable.

