Remix.run Logo
mattmanser 9 days ago

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.