Return statement is ok

macsikora

Pragmatic Maciej

Posted on October 9, 2021

Return statement is ok

Hey, long time no see. I have a new fancy subject, very much related to the code style, so the subject which developers like to argue about. My motivation to write this article is the eslint rule which I've lately found in the codebase, exactly - arrow-body-style. So we will talk about - using or not using "return" statements in expression based arrow functions.

dev argue

Arguments against explicit return

Few points which will tell us why we have this rant at all.

Explicit return is less readable.

JS has return statement, yes it does, even though features like arrow functions allow for not using it, we cannot magically make JS a language without it. Why? Because we have statements, and when we need to use them, we need to use return statements also.

Consider below two code examples:

let hateReturn = (user) => (
  hasDog(user) ? "Dog lover" : "Cat lover";
)

let returnIsOk = (user) => {
  return hasDog(user) ? "Dog lover" : "Cat lover";
}
Enter fullscreen mode Exit fullscreen mode

Is there any difference between them in terms of readability? I don't think there is any difference. Do you think "return" very much makes this code worse? I don't think so. Do you think the second form should be somehow forbidden? I hope not.

Arrow functions should be consistent and not use return

Yes, great but is it even possible? Let's add another function.

let hateReturn = (user) => (
  hasDog(user) ? "Dog lover" : "Cat lover";
)

let returnOk = (user) => {
  return hasDog(user) ? "Dog lover" : "Cat lover";
}

let anotherReturnOk = (user, max) => {
  const distance = calculateDistance(user);
  if (distance > max) {
    return "Too far";
  } else {
    return "Close enough";
  }
}
Enter fullscreen mode Exit fullscreen mode

How do you see these three functions together? Which ones are consistent in syntax? Looks like last two have the same brackets, the same form of returning, and the hateReturn has no curly brackets but round ones, it has also no return.

There is no chance that all your functions will not have a return statement. And for sure having some of them with curly brackets and returns, and some of them with round ones doesn't make the code consistent.

Note Yes you can ho hardcore and make you own language by creating all statements as expressions, but we don't talk about such ;)

Note Yes we can use standard function declaration and not be bothered by arrow functions. Then there is no choice to make.

With "return" there is more code

Yes it is, so what?

Arrows without return are cool

Yes, they are...
fancy


Arguments for explicit return

Now counter attack. Beware.

Flexibility issues with implicit return

Flexibility
What if our function needs to have additional logic included? Ye we can try to keep implicit return by keeping conditional expression:

let hateReturn = (user) => (
  hasDog(user) 
    ? "Dog lover" 
    : hasCat(user) 
      ? "Cat lover"
      : "No animals"
)
Enter fullscreen mode Exit fullscreen mode

Take a look at the formatting which even better allows us to read this. Is it bad? I don't think it is, but what if I would like to make it more explicit with "if" instead of ternary? (Un)fortunately we need to rewrite the function and use explicit return then:

let returnIsOk = (user) => {
  if (hasDog(user)) {
    return "Dog lover"
  } 
  if (hasCat(user) {
    return "Cat lover"
  }
  return "No animals"
}
Enter fullscreen mode Exit fullscreen mode

We used a technique called "early return" here. And again is the second version the worst? I don't think so. And it is not a rant aiming into ternaries, no, it only shows that we are less flexible with implicit returns, and we need to do a lot of gymnastics to keep it.

Additional variables

Assignment is a statement, therefore you cannot use it in arrow function without return. Consider two code examples:

let hateReturn = (user) => (
  user.dogs + user.cats > 5 && user.flat_size < 50 
    ? "Cannot have more animals" 
    : "Can have more animals"
)
Enter fullscreen mode Exit fullscreen mode
let returnIsOk = (user) => {
  const manyAnimals = user.dogs + user.cats > 5;
  const smallFlat = user.flat_size < 50;

  if (manyAnimals && smallFlat) {
    return "Cannot have more animals"
  } else {
    return "Can have more animals"
  }
}
Enter fullscreen mode Exit fullscreen mode

Unfortunately any local variables for better readability cannot happen in the implicit return version.

Note Expression based languages like Elm, Haskell have special expression for having local constants. In that way we can use expression and still make code more readable. Below example from Elm:

-- ELM LANGUAGE --
let
    manyAnimals = 
      user.dogs + user.cats > 5
    smallFlat =
      user.flat_size < 50
in
  if manyAnimals && smallFlat then 
    "Cannot have more animals"
  else
    "Can have more animals"
Enter fullscreen mode Exit fullscreen mode

And it is an expression, there is also the if expression visible above. No statements but code looks almost the same as the one with statements. Also no explicit return, but this is not JS and there is no return keyword.

Do you return or not dilemma

Two functions close to each other

let bomb = (bomb) => {
  api.post.sendBomb(bomb); // doesn't return
}

let secondBomb = (bomb) => (
  api.post.sendBomb(bomb); // does return
)
Enter fullscreen mode Exit fullscreen mode

Looks like no difference, but because of different brackets we have different returns, first function returns void, second returns what sendBomb is returning. Quite an implicit difference don't you think? You need to look at brackets to understand if there is return or not.

Object returning

That is I think a legendary example, my first impression years ago was - what a hack, they introduced new syntax and created such an issue. How do we return objects by implicit return?

let f = (a) => {a} // this has no return
let f2 = (a) => ({a}) // returns object with key 'a'
Enter fullscreen mode Exit fullscreen mode

That always made me sad. Syntax confusion is high, therefore I see no issue in doing:

let f3 = (a) => { 
  return {a}; 
}
Enter fullscreen mode Exit fullscreen mode

Yes the longest of them all, but there is no confusion.

I want to use hooks in React

const Component = ({name}) => (
  <div>
    {name}
  </div>
)
Enter fullscreen mode Exit fullscreen mode

Great, now what if we need to use some hook inside? Yes, unfortunately we need to rewrite the function:

const Component = ({name}) => {
  const [show, setShow] = useState(true);
  return {show && <div>
    <button onClick={() => setShow(true)} >Hide</button>
    {name}
  </div>}
}
Enter fullscreen mode Exit fullscreen mode

There is no way to avoid explicit return. We need to rewrite the component, change brackets, add return. Maybe not much, but for me it is always a burden.

But wait, onClick there is an arrow function, it has no explicit return, there you go, you have used it - looser. Yep I am using implicit returns of arrow functions when I see it as a best fit, for example for function arguments in many cases we already have a closure with all needed data, we don't need any local variables, in most they are simple functions. So yes I see no issue in using implicit return, but the whole rant is not about not using it, but about forbidding using the return for expression based functions. I see no valid reason to forbid it, in the same way I see no reasons to not use standard function syntax.

In summary if you have arrow-body-style rule in your project, turn it off. Don't create not needed code restrictions.

Note that the whole article is about JS only. If language does not have explicit return - good, there is no problem and no decision making.

💖 💪 🙅 🚩
macsikora
Pragmatic Maciej

Posted on October 9, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Return statement is ok
javascript Return statement is ok

October 9, 2021