Personally I wouldn't agree with this. I've adopted a pattern where I try to only ever return the success value at the end of a function. Early returns of success value don't feel clear to me and make the code hard to read. I think that sort of code should only be used if you need high performance. But for clarity, it hurts.
Instead I think you should generally only use early returns for errors or a null result, then they're fine. Ditto if you're doing a result pattern, and return a result object, as long as the early return is not the success result (return error or validation errors or whatever with the result object).
So I feel code like this is confusing:
function CalculateStep(value) {
if(!value) return //fine
///a bunch of code
//this early return is bad
if(value < 200) {
//a bunch more code
return [ step1 ]
}
///a bunch more code
return [ ..steps ]
}
The early return is easy to miss when scanning the code. This is much less confusing:
function CalculateStep(value) {
if(!value) return //fine
///a bunch of code
let stepsResult : Step[]
if(value < 200) {
//a bunch more code
stepsResult = [ step1 ]
} else {
//a bunch more code
stepsResult = [ ..steps ]
}
//In statically typed languages the compiler will spot this and it's an unnecessary check
if(!stepsResult) throw error
//cleanup code here
return stepsResult
}
It makes the control flow much more obvious to me. Also, in the 2nd pattern, you can always have your cleanup code after the control block.
Instead I think you should generally only use early returns for errors or a null result, then they're fine. Ditto if you're doing a result pattern, and return a result object, as long as the early return is not the success result (return error or validation errors or whatever with the result object).
So I feel code like this is confusing:
The early return is easy to miss when scanning the code. This is much less confusing: It makes the control flow much more obvious to me. Also, in the 2nd pattern, you can always have your cleanup code after the control block.