Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return-early rule #16

Open
kevva opened this issue Apr 23, 2016 · 14 comments
Open

return-early rule #16

kevva opened this issue Apr 23, 2016 · 14 comments

Comments

@kevva
Copy link

kevva commented Apr 23, 2016

As discussed in sindresorhus/execa#15 (diff). How I think when coding is to handle errors and exceptions as soon as possible and return early. If we take a look at the two examples below, the latter one makes more sense to me. foobar() in this case is the "real" body while the code in the if statements deal with exceptions. When using the style in the former one, this isn't as clear to me.

if (conditon) {
    someFn();
} else if (someOtherCondition) {
    someOtherFn();
} else {
    foobar();
}
if (condition) {
    someFn();
    return;
}

if (someOtherCondition) {
    someOtherFn();
    return;
}

foobar();

This might be worth reading too http://blog.timoxley.com/post/47041269194/avoid-else-return-early.

@SamVerschueren
Copy link
Contributor

Like that style as well! Would be nice.

@jfmengels
Copy link
Contributor

👍 for the idea.

I found this recently https://github.com/Shopify/javascript/blob/master/packages%2Feslint-plugin-shopify%2Fdocs%2Frules%2Fprefer-early-return.md, which handles a related case.

I would also handle the following:

// handled by the above mentioned plugin
function foo() {
  if (condition) {
     ...
  }
  // nothing else
}

but not

function foo() {
  let value;
  if (condition) {
     value = X;
  } else if (condition2) {
     value = Y;
  } else {
     value = Z;
  }
  // do something with value like
  return foo(value);
  // return value or anything else
}

// the alternative would be
function foo() {
  let value;
  if (condition) {
     return foo(X);
  } else if (condition2) {
     return foo(Y);
  }
  return foo(Z);
}
// which is longer to refactor

@jfmengels
Copy link
Contributor

I think I've already been bitten by http://eslint.org/docs/rules/no-negated-condition, that made it not possible to return early, so in XO you may have to relax the rule a bit (or create a new rule that handles both cases?).

@jamestalmage
Copy link
Contributor

no-negated-condition does not prevent the style @kevva is proposing. It just tells you to un-negate the condition and flip the consequent and alternate blocks.

@jfmengels
Copy link
Contributor

True, but it does in the case of my first example, which I think would be nice to also handle in this plugin (though it doesn't have to as there already exists a rule for it).

@jamestalmage
Copy link
Contributor

I disagree with the rule as proposed. I do agree with the sentiments from @timoxley's blogpost. But those are different from the code being discussed in sindresorhus/execa#15 (diff).

In Tim's blog he recommends against this:

function (err, result) {
  if (err) {
    handleError(err);
  } else {
    processTheResult(result);
    processTheResultMore(result);
    stillMore();
    // ...
  }
}

In that case I agree with the refactor to use a return statement. You are saying: "If there is an error,handle it, but ignore the rest of this - the remaining function is moot because there is an error". Also, for every line you get to un-indent in that else block, using the return adds more value.

Up until this point, I'm in agreement with everyone I think.

Where I disagree is the simple if / else statement referenced:

function (input) {
  if (isStream(input)) {
    handleStream(input);
  } else {
    handleString(input);
  }
} 

In this case you're not bailing (returning) early because there is an error. There are two branches your code could take, and that is clearly reflected with the explicit if / and else blocks. The shape of your code reflects the flow. Adding the return only allows you to un-indent a single line.

IMO - prefer-early-return with some maxStatements set represents a pretty good compromise.

@fregante

This comment has been minimized.

@fregante
Copy link
Collaborator

This could/should also apply to continue.

Incorrect

for (x in y)
	if (isGreen(x))
		if (isBold(x))	
			if (isItalics(x))
				console.log('All good')

Correct

for (x in y) {
	if (!isGreen(x))
		continue

	if (!isBold(x))
		continue

	if (!isItalics(x))
		continue

	console.log('All good')
}

@kkmuffme
Copy link

There are similar rules for PHP code (https://github.com/slevomat/coding-standard#slevomatcodingstandardcontrolstructuresearlyexit-) and I think it would make a lot of sense here too, especially to avoid nested tons of nested ifs.

Generally: (what I came up with when checking some code samples)
if we are in function context and an "if" does not have an "elseif", we can return/continue instead of nesting more. (if it has an elseif, all direct children "if" of if/elseif cannot be changed without breaking the logic, but all children "if" more than 1 level down, can again have this rule applied).

Logic:

  • "if" contains an "if" but itself is not directly followed by an "elseif" - code can be simplified to return early/continue.
  • "if" contains an "if" but itself is directly followed by an "elseif" - do not give an error for any "if" that are direct children of this "if"
    (attention: the "if" do not need to be in lines directly after each other, but it's more like parent-child level)
if ( foo )
   if ( bar ) => give error for this
   
   
if ( foo )
   if ( bar )
       if ( qwe ) => give error for this
elseif ( xyz )
   if ( abc )
       if ( bla ) => give error for this

@thigrand
Copy link

Any update on this topic? Do anyone know is there a rule like that implemented somewhere?

@belgattitude
Copy link

@thigrand might answer your question: https://github.com/Shopify/web-configs/blob/main/packages/eslint-plugin/lib/rules/prefer-early-return.js.

@karlhorky
Copy link

@belgattitude (also further up in the thread @jfmengels) nice find! Some additional details:

@fregante
Copy link
Collaborator

fregante commented Nov 22, 2023

I tried that plugin. I think this should be allowed:

function bar() {
	if (foo) {
		onlyStatementHere()
	}
}

It does "unnecessarily" increase indentation, but I think changing it to this is kind of annoying and not any more readable:

function bar() {
	if (!foo) {
		return;
	}

	onlyStatementHere()
}

However in this case it starts to tip the scale a bit:

function bar() {
	if (foo) {
		many()
		other.statements = 1;
		if (here) {
			log(lotsOfLines)
		}
		how = 'many?'
	}
}

Maybe optionally it can allow it as long as there are no further statements outside the condition.

  • 👆 allowed ✅
  • 👇 not allowed ❌
function bar() {
	if (foo) {
		many()
		other.statements = 1;
		if (here) {
			log(lotsOfLines)
		}
		how = 'many?'
+		return;
	}

+	return 'nah'
}

@kkmuffme
Copy link

Just set maximumStatements: 1, in the config for that rule and it won't report an error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants