DEV Community

Michael Kovacevich
Michael Kovacevich

Posted on

Ditching the "else" Statement

Are "else" statements really necessary? When first learning to program we are taught about "if" statements and "else" statements. If this condition is met do something, else do another thing. It's fundamental to nearly every programming language, but can we ditch the "else" statement?

The Case for Ditching It

Like with most things in programming there will be scenarios where using an "else" statement is more appropriate than avoiding it, but let's assume that we want to ditch it no matter what scenario arises. How would we do that and why would we want to?

The best way to explore this approach is through examples so let's start with a simple one. All of the examples below are written in Typescript:

// With "else"
const areEqual = (value1: string, value2: string) => {
  if (value1 === value2) {
    return true;
  } else {
    return false;
  }
};
Enter fullscreen mode Exit fullscreen mode
// Without "else"
const areEqual = (value1: string, value2: string) => {
  if (value1 === value2) {
    return true;
  }

  return false;
};
Enter fullscreen mode Exit fullscreen mode

This is a common scenario where the "else" statement should be omitted because it is adding more code without adding value. Of course, this entire function is adding code without adding value, since you could just use the "===" operator directly, so let us complicate things a bit more. What if we don't want to "return" from inside our if block?

const getFullName = (
  givenName: string,
  lastName: string,
  preferredName?: string
) => {
  let firstName = "";
  if (preferredName) {
    firstName = preferredName;
  } else {
    firstName = givenName;
  }

  return `${firstName} ${lastName}`;
};
Enter fullscreen mode Exit fullscreen mode

There are several ways to get rid of that "else" statement which also improve the readability of the function. For example, a ternary can be used in these situations:

const getFullName = (
  givenName: string,
  lastName: string,
  preferredName?: string
) => {
  const firstName = preferredName ? preferredName : givenName;
  return `${firstName} ${lastName}`;
};
Enter fullscreen mode Exit fullscreen mode

This has the added benefit of letting us use a "const" instead of a "let", but wait a minute, this is cheating! A ternary is essentially an "if/else" statement with less syntax, so we didn't really ditch the "else" statement!

Let's try again:

const getFullName = (
  givenName: string,
  lastName: string,
  preferredName?: string
) => {
  const firstName = getFirstName(givenName, preferredName);
  return `${fullName} ${lastName}`;
};

const getFirstName = (givenName: string, preferredName?: string) => {
  if (preferredName) {
    return preferredName;
  }

  return givenName;
};
Enter fullscreen mode Exit fullscreen mode

That is much better, but for the minimalists out there you could also write it this way:

const getFullName = (
  givenName: string,
  lastName: string,
  preferredName?: string
) => `${getFirstName(givenName, preferredName)} ${lastName}`;

const getFirstName = (givenName: string, preferredName?: string) =>
  preferredName || givenName;
Enter fullscreen mode Exit fullscreen mode

The real sticklers will note that this could be done in one function and would reduce the lines of code and the number of characters used:

const getFullName = (
  givenName: string,
  lastName: string,
  preferredName?: string
) => `${preferredName || givenName} ${lastName}`;
Enter fullscreen mode Exit fullscreen mode

This is a great little function, but in combining the two functions into one the reusability has gone down. What if we only want to get the first name? We no longer have a dedicated function to do it! Of course in a real code base simply using the "||" operator to pick between the two values would be acceptable and would result in less code:

const fullName = `${preferredName || givenName} ${lastName}`;
Enter fullscreen mode Exit fullscreen mode

However, their is also a readability trade off since having a specific function makes it clear what operation is being executed. This along with the reusability is why the dedicated functions is preferred.

Alright enough talk about minimizing code, let's get back to the "else" conversation. However you choose to implement functions similar to those above, you do not need an "else" statement to accomplish the task. Both examples here show the two main ways to avoid using an "else" statement: "returning early" and "extracting smaller functions".

Returning early is a straight forward concept. The idea is to return your result (including void) as soon as you can, in other words, minimize the amount of code that needs to be executed. If we return early we can omit "else" and "else if" statements as they just become redundant.

Extracting smaller functions should be a familiar technique to most software engineers. In both Functional and Object Oriented Programming this is often referred to as the Single Responsibility Principle, the first of the SOLID Principles. We want our functions, classes, and methods to have a single purpose/responsibility. Many engineers make the mistake of defining a single responsibility with a scope that is far too big, resulting in classes, methods, and functions that are large and have many smaller responsibilities. Every large responsibility is made up of smaller responsibilities, and the more of those we identify and isolate, the closer we are following the Single Responsibility Principle.

Following these two techniques will result in fewer "else" statements without even trying. Sounds like if we write our code with good fundamentals we should never need an "else" statement, so why use them at all?

The Case for Keeping It

The only real case for continuing to use "else" statements is convenience, and this is a pretty strong argument. Whether you are working within a legacy code base or just rushing to make a tight deadline, "else" statements are the last things on your mind. However, I still hardly use "else" statements. Not because I'm consciously thinking about it, but because I usually keep my functions small and single purposed, so the need for "else" statements rarely arises. From time to time an "else" statement or two isn't a big deal, but only when it's convenient.

So how often does this convenience arise? Unfortunately, very often, especially in legacy code bases. Finding an existing code base that is strictly following best practices and conventions is a rarity, and honestly that's a good thing for technological progress. Innovation lies somewhere between idealism and pragmatism, as does the decision to use an "else" statement or not.

Conclusion

End of the day, do what is best for you and your team. Most legacy code bases I've seen have plenty of technical debt beyond the number of unnecessary "else" statements being used. I would however encourage everyone reading this to create a Standards and Conventions document for your team. Depending on the size and experience the team, the document may be very small with just enough detail to let the engineers know what is expected of them. For medium to large teams a document like this is essential and should be constantly referenced, maintained, and updated. The standards and conventions you add over time will show the progress that your team has made towards having a higher quality code base. Maybe you'll even include "No Else Statements" as a standard, but probably not.

Discussion (14)

Collapse
jonrandy profile image
Jon Randy

Interestingly, when I was briefly on a Computer Science degree (that turned out to be a waste of time) - we were taught that early returns / guard clauses were considered very poor style, and that every function should have only one return statement

Collapse
eljayadobe profile image
Eljay-Adobe

That's one of the commandments from the structured programming era of programming. Apparently some professors are still living in that era.

Collapse
aminmansuri profile image
hidden_dude

Well it's interesting to explore WHY they stressed this.

In the Assembly language world having jumps all over the place was a real problem. In the grand scheme of evolution this was an important principle.

Of course higher level languages made this point moot (in my opinion). And in fact, returning early often saves you from having to deal with flags which are really a horrible programming style.

Collapse
jonrandy profile image
Jon Randy

When I had that class would have been around '95

Thread Thread
eljayadobe profile image
Eljay-Adobe

That'd be about right. 1950s structured programming approaches, which were refined and formalized in the 1960s, being taught in 1995.

There are languages, like COBOL, where the language itself doesn't allow you to violate the one return statement. Fortunately, JavaScript is not COBOL.

Collapse
mkovace profile image
Michael Kovacevich Author

I have a CS degree and I was taught that as well...unfortunately most CS professors don't have much recent industry experience.

Returning early can be very dangerous if you aren't keeping your functions small. If your functions are more than 10-20 lines of code, I'd avoid multiple returns because you'll create a bug factory.

Collapse
jimstockwell profile image
Jim Stockwell

Yes, well said: when it’s dangerous and when it’s not.

Collapse
assunluis80 profile image
Luís Assunção • Edited on

Exactly! When I was a CS student, I remember the teachers stressing out about multiple return statements... It completely changes the internal organization of compilers.

Beside of that, I would write the first example as just:

return (value1 === value2); 😎

Collapse
mkovace profile image
Michael Kovacevich Author

Yea I pointed out that the function itself is useless, but it serves as a simple example.

As far as it's effects on the compiler, I'd say it very much depends on the language and the use case. Also in my 7 years in industry I've never had to take a minor compiler optimization like that into consideration.

Collapse
haaxor1689 profile image
Maroš Beťko • Edited on

I feel like you did not provide complex enough example to show of what a huge difference in code readability can early return make.

Let's say we have some complicated fetch that needs to first get some ticket and then use it in a 2nd request.

const fetchData = async (id?: string) => {
  if (id) {
    const ticketResponse = await fetch('api/ticket');
    if (ticketResponse.ok) {
      const response = await fetch (`api/data?t=${await ticketResponse.text()}`);
     if (response.ok) {
       return await response.json();
     } else {
       return 'Response fetch error';
     }
    } else {
      return 'Ticket fetch error';
    }
  } else {
    return 'No id';
  }
}
Enter fullscreen mode Exit fullscreen mode

Now this is a horrible code to read because for every if you need to look for corresponding else and you need to constantly jump between lines when trying to understand the code.

Why early return is so good is because with it, every time an error occurs that should end the function in earlier step, you can return from the function and not think about that branch of execution after.

const fetchData = async (id?: string) => {
  if (!id) {
    return 'No id';
  }

  const ticketResponse = await fetch('api/ticket');
  if (!ticketResponse.ok) {
    return 'Response fetch error';
  }

  const response = await fetch (`api/data?t=${await ticketResponse.text()}``);
  if (!response.ok)  {
    return 'Ticket fetch error';
  }

  return await response.json();
}
Enter fullscreen mode Exit fullscreen mode

And this is the main reason for early return.

  • No unnecessary indentation
  • When reading, you don't need to jump between lines to find the other execution branch
  • Any errors/cases that causes function to not execute all steps returns and you don't need to think about that case after that since it's handled
Collapse
peerreynders profile image
peerreynders

A ternary is essentially an "if/else" statement with less syntax, so we didn't really ditch the "else" statement!

Strictly speaking the conditional operator is an expression, not a statement like if…else. Given that it's an expression it has to return a value which is why the "else" expression is mandatory. So in this case "else" is a feature not a defect because it forces you to deal with either outcome explicitly — similar to exhaustiveness checking in TypeScript's discriminated unions.

  • (if) statements (with or without else) are the hallmark of Place-Oriented Programming
  • (conditional) expressions are part of Value-Oriented Programming.

In Value-Oriented Programming you always have to deal with "else" even if it's undefined.

Collapse
phlash909 profile image
Phil Ashby

I found a decent thread on stack exchange where single entry, single exit (SESE) is discussed, the top answer provides a nice history lesson (it was assembly & FORTRAN that required the statement on SESE from Dijkstra as these allow a function to have multiple entry points, and to return to different locations - gotos everywhere!), the follow on answer brings us up to date with issues around resource management in non-garbage collected languages (eg: C/C++ although there are techniques to fix this such as guard objects in C++).

softwareengineering.stackexchange....

Collapse
stiby profile image
stiby

The article is an example of the action of refactoring, I'd recommend reading Martin Fowlers Refactoring book which talks about the more advanced patterns you're starting to find which can't be automated.

When working as a team having well understood practices and principles like design patterns and refactoring patterns aids conversation, especially when reviewing code.

Collapse
mkovace profile image
Michael Kovacevich Author

Very familiar with Fowler and other authors in this space, good recommendation. Wanted to keep this article simple and accessible. Thinking I might dig a little deeper into my knowledge and experience next time.