For me it depends how "surprising" the return is.
There are certain cases - validating input parameters, checking that you're in the right state for the operation to be possible, where early exit is normal and expected. I wouldn't use an else clause for those.
But if you're half way through the method and want to exit early, the code will sometimes be easier to read with an if/else, since it makes it abundantly clear that you are dividing the world into two different cases, and doing different things in each case.
My rule of thumb would be that if it's basically arbitrary which case is the early exit, and which exits at the end of the routine, then the early exit is probably "surprising". Otherwise it may be "natural". So for instance the following two bits of code look structurally different even though they're equivalent:
if (no_can_do) return false;
do;
things;
return true;
// ------------------------
if (!no_can_do) {
do;
things;
return true;
}
return false;
The second snippet is clearly evil, because it sends you off reading the end of the function for no good reason. So the early exit in the first snippet is not surprising, it's natural.
The following two bits of code look basically the same.
if (case1) {
do;
things;
return true;
}
do;
different things;
return false;
// ------------------------
if (!case1) {
do;
different things;
return false;
}
do;
things;
return true;
Because it's arbitrary which case is the early exit, the early exit is therefore surprising. The nature of the function, that it handles two non-trivial cases, would perhaps better be highlighted by doing:
if (case1) {
do;
things;
return true;
} else {
do;
different things;
return false;
}
instead. You might even write two separate routines here, one to handle case1 and the other to handle the rest, and just call one or the other.