DEV Community

Abdul Haseeb
Abdul Haseeb

Posted on • Updated on

Do we even need if/else?

Flow control is one of the first things we all learn as programmers.
We are going to learn about some of the alternatives we have, that from my point of view are generally more cleaner and safer.

clean code

Lets take a simple example to start with;

if/Ternary

const isWeekend = (day) => {
  let message;
  if (day === 'sunday') {
      message = "Its a weekend";
  }
  return message;
};
Enter fullscreen mode Exit fullscreen mode

We have a function isWeekend which takes a dayand returns if its a weekend or not. Now this has an issue, but JavaScript itself doesn't give us any kind of error. We didn't return any message if its not sunday. So we can do something like this or add an else block:

const isWeekend = (day) => {
  let message = 'Its a working day :(';
  if (day === 'sunday') {
      message = "Its a weekend";
  }
  return message;
};
Enter fullscreen mode Exit fullscreen mode

Now, as As the title says, do we even need if for this simple conditional block? No, we can use a ternary instead.
So we can update our function isWeekend like this:

const isWeekend = (day) =>
  day === "sunday" ? "Its a weekend" : "Its a working day :(";

// or

const isWeekend = (day) =>
  'Its a ${ day === "sunday" ? "weekend" : "working day :(" }';

Enter fullscreen mode Exit fullscreen mode

Advantages of ternaries over ifs:

  • It forces to cover both if and else case.
  • Less code footprints.
  • More readable.
  • Another big advantage is that we can initialize a constant based on condition i.e.
const a = condition? 'value1' : 'value2';
Enter fullscreen mode Exit fullscreen mode

We cannot achieve this using if else and will have to use let instead of const.

if/Switch

But what if we have to cover multiple conditions. Instead of using multiple ifs we should use the switch statement. Let take the same example, this time we need to have a condition for all the possible days i.e.

// multiple case switch program
  switch (day) {
    case "monday":
    case "tuesday":
    case "wednesday":
    case "thursday":
    case "friday":
      return "Its a working day :(";
      break;
    case "saturday":
    case "sunday":
      return "Its a weekend";
      break;
    default:
      return "thats not even a day";
      break;
  }
Enter fullscreen mode Exit fullscreen mode

Plain Objects and the ??

We can even use plain objects and the nullish operator.

const daysMap = (day) =>
  ({
    "monday": "Its a working day :(",
    "tueday": "Its a working day :(",
    "wednesday": "Its a working day :(",
    "thursday": "Its a working day :(",
    "friday": "Its a working day :(",
    "saturday": "its a weekend",
    "sunday": "its a weekend",
  }[day] ?? "thats not even a day");

const isWeekend = ( day ) => daysMap(day);
Enter fullscreen mode Exit fullscreen mode

Those who are not familiar with the ?? operator, it checks either it has a value or nullish (null or undefined). If day is nullish, then we use defaultValue, it not, use the value itself.

Conclusion:

There might be some cases where we have to use if/else. But, in my opinion, we can use the alternatives in most cases.

Any thought??

#coding #softwareengineering #productivity #cleancode #codingtips #javascript #webdev #devlife #programming #computerscience

Discussion (18)

Collapse
joelbonetr profile image
JoelBonetR • Edited on

Yes of course we need if/else statements.

  • Using ternary operator you simply chain if - else if statements the bad way under the hood which is not always the best option. It is good for a single comparison with a fallback or 2 as much.

foo === 'x' ? doSomething() : foo === 'z' ? doSomethingElse() : fallback()

equals:

if (foo === 'x') {
  doSomething();  
} else {
  if (foo === 'z') {
    doSomethingElse();
  } else {
    fallback();
  }
} 
Enter fullscreen mode Exit fullscreen mode

you can quickly spot the problem here, specially when chaining more options.
On the other hand if you only need to handle an action if something evaluates to true, there's no point on using a ternary:

if (foo === true) 
  doWhatever();
Enter fullscreen mode Exit fullscreen mode

cannot be translated into a ternary, simply because you don't need to do a different thing in case the expression evaluates to false, it would be somewhat weird (and wrong) to do it like:

foo === true ? doWhatever : '';
Enter fullscreen mode Exit fullscreen mode
  • Using switch case you only provide an action, also not valid for every operation. Several times you need to ensure multiple conditions. Simple example: needing to check if a user has a paid account and then checking if this account is a premium account or a regular paid one, what you do? chaining switch statements? that would be a mess. Chaining a ternary after the switch is also not an option for the last example in ternary concerns.

*As side-note, switch statements are harder to optimise by the JIT as well, in comparison with if-else statements.

  • To use the Nullish coalescing operator ?? operator you don't even need an Object, i.e.
const foo = null ?? 'default string'; 
Enter fullscreen mode Exit fullscreen mode

is valid as well but as you can imagine, that does not fit for any case.

So yes, we still need if else statements and they do the job pretty well in many cases.

Collapse
pengeszikra profile image
Peter Vivo • Edited on

Depend on format, imho much better readable than if plus ternary give a result, do not need use let or var

const result = foo === 'x'
  ? doSomething() 
  : foo === 'z' 
    ? doSomethingElse() 
    : fallback()
  ;
Enter fullscreen mode Exit fullscreen mode
Collapse
joelbonetr profile image
JoelBonetR • Edited on

I can't understand why y'all focus on readability in the first hand. Even Uncle Bob told us that the first half of a Dev's job is to make things work and the second half is to make it clean and readable.
You need to check first that your code covers the use cases and it's right (in the functional sense) and then you can re-write it on a more readable way (if any).

I'll set a funny working example on how to hash a string in JS using the Bitwise OR assignment in combination with the Left Shift Operator :

String.prototype.hash = function () {
  'use strict';
  var h = 0, i;
  if (this.length === 0) return h;
  for (i = 0; i < this.length; i++) {
    h = (h << 5) - h + this.charCodeAt(i);
    h |= 0;
  }
  return h;
}
Enter fullscreen mode Exit fullscreen mode

now you can do myString.hash(). Enjoy.
If you're too afraid of ES releasing a hash() method you can use it as function as well

hash(str) { 
  'use strict';
  var h = 0, i;
  if (this.length === 0) return h;
  for (i = 0; i < this.length; i++) {
    h = (h << 5) - h + this.charCodeAt(i);
    h |= 0;
  }
  return h;
}
Enter fullscreen mode Exit fullscreen mode

and call it like myString = hash(myString)

It works, i states for index and h states for hash.
You can change the variable names if you want to "index" and "hash" but can it be more readable than that? Let's let this question on air so you can answer with your ideas.

My guess is that unless you know how those operators work, you'll not fully understand it, on the other hand if you know'em, it will be obvious to you.

Thread Thread
pengeszikra profile image
Peter Vivo • Edited on

I think readability help take look our code even few month later.

Imho your hash algorythm looks like this:

const hash = str => [...str]
  .map(chr => chr.charCodeAt(0))
  .reduce((acu, code) => ((acu << 5) - acu + code) | 0, 0)
  ;
Enter fullscreen mode Exit fullscreen mode
Thread Thread
joelbonetr profile image
JoelBonetR • Edited on

That's beautiful, and it can be provided as string method like:

String.prototype.hash = function () {
  return [...this]
  .map(chr => chr.charCodeAt(0))
  .reduce((acu, code) => ((acu << 5) - acu + code) | 0, 0)
}
Enter fullscreen mode Exit fullscreen mode

This last one adds the requirement of understanding map and reduce and the one above the knowledge of basic if operator and for loop

That's just what we were talking about. Either the one in my post above or yours reduced example will be easy to understand if you know the operands but hard a.f. if you don't.

Readability is objective till some point where it turns subjective (by the knowledge and experience PoV) and that's the main reason of this cyclic discussions imo 😆

Someone whith basic or zero understanding on map would say yours is shit and someone with no knowledge of bitwise operators would complain of mine as well. The result in terms of readability can vary but none of those examples have a single thing wrong in readability terms, it has more or less lines of code (which is never equal to readability) and different operators or functions to reach the same. Which is more readable? Some will say 1 some will say 2. The important thing here is that both provide (unless I miss something) the exact same result for a same entry value.

Thread Thread
pengeszikra profile image
Peter Vivo

That is the reason to know more about .map .filter .find and .reduce functionality in js. After basic one like if/else, for, while, switch. These functionality is lead to functional programming direction.

Any way

String.prototype.hash =
Enter fullscreen mode Exit fullscreen mode

means you declare one global dependency on your code. I think much better to forget this type of work. Because this declaration influence your team whole application.

Thread Thread
joelbonetr profile image
JoelBonetR

It will be available just when you import the file containing it, not less not more. It just depends on the project architecture. The only concern about setting string (or other data structure) methods like this is the possibility of ES adding a method with the same name and thus having to refactor it everywhere it's used (or deleting it if it's provided along the language)

Collapse
lukeshiru profile image
LUKESHIRU • Edited on

I talked about this on my post, but if you have something like this:

const example =
    foo === "x" ? doSomething : foo === "z" ? doSomethingElse : fallback;

example(value)();
Enter fullscreen mode Exit fullscreen mode

That's a sign you need to move some logic away, so it actually should look more like this:

const otherCases = foo => (foo === "z" ? doSomethingElse : fallback);

const example = foo === "x" ? doSomething : otherCases(foo);

example(value)();
Enter fullscreen mode Exit fullscreen mode

As soon as you need to nest logic, that means that you need to move that logic away, so even if it looks like a "limitation" of ternaries, it can be taken as yet another feature because you keep different logic blocks separated, instead of having lots of different concerns in the same function.

I also mentioned in my post that I see the "mandatory else" in a ternary as a feature, not a problem. You have to cover all the possible logic branches, so you can't do something like:

if (foo) {
  return doSomething();
}
Enter fullscreen mode Exit fullscreen mode

Without being explicit about what to do with the else branch. Maybe you intentionally want to leave that empty, but to do so you need to be explicit, which from my point of view is great:

return foo ? doSomething() : undefined;
Enter fullscreen mode Exit fullscreen mode

Still, the point in my post is not that you don't ever need if, the point is that you might not need it always, and to reconsider some of the logic you're about to write instead of just defaulting to if for conditions, for for loops and so on.

Cheers!

Collapse
joelbonetr profile image
JoelBonetR • Edited on

Having a chained ternary is not always wrong. Chaining them more than 2 or 3 times are usually wrong because you'll rely on checks that are dependant on each other and you'll probably need different chained ternary blocks to reach the expected use cases so it's better to use if/else but

foo === "x" ? doSomething : foo === "z" ? doSomethingElse : fallback;
Enter fullscreen mode Exit fullscreen mode

There's nothing wrong here as long as this covers your use cases properly. If you don't need the fallback then this is wrong but if this is just what you need then go ahead with that, we should be fine.

const otherCases = foo => (foo === "z" ? doSomethingElse : fallback);

const example = foo === "x" ? doSomething : otherCases(foo);

example(value)();
Enter fullscreen mode Exit fullscreen mode

This is supposed to be equivalent to the code above but instead you add a function declaration and a variable to reach... pretty much the same?
This is absurd. An extreme lack of knowledge about logic gates and inefficient way of working just because "you like how it looks".

Then here

foo ? doSomething() : undefined;
Enter fullscreen mode Exit fullscreen mode

Why should you state "undefined" just because foo is falsy?
You are not even setting this value into a variable to check later (it can be ok depending on the use case). instead you're just saying:

if (foo) doSomething();
else undefined;
Enter fullscreen mode Exit fullscreen mode

The question now is... undefined what? What's the point? What should we do with this undefined?

Again your starter point is "I like how it looks" not an understanding on how things work below.

Thread Thread
lukeshiru profile image
LUKESHIRU • Edited on

Having a chained ternary is not always wrong.

I'm not saying is wrong, is just harder to read. You have to format it like Peter shown on this comment, and it might still be kinda hard to read.

This is supposed to be equivalent to the code above but instead you add a function declaration and a variable to reach... pretty much the same?
This is absurd. An extreme lack of knowledge about logic gates and inefficient way of working just because "you like how it looks".

Is not about the looks, is about separation of concerns and reuse. By moving away branches of logic from one function to another, you're effectively making that branch reusable by other function and you're simplifying the logic of the function that previously had all the logic on it. Let me show you a more "real" example, maybe that clicks better for you:

// Instead of having all the logic in the same function:
const navigation = user =>
    (user.loggedIn
        ? user.canMod
            ? user.isAdmin
                ? adminNavigation
                : modNavigation
            : userNavigation
        : visitorNavigation)(user);

// And then having to check the same things every time, you can just...
const canModNavigation = user =>
    (user.isAdmin ? adminNavigation : modNavigation)(user);

const loggedInNavigation = user =>
    (user.canMod ? canModNavigation : userNavigation)(user);

const navigation = user =>
    (user.loggedIn ? loggedInNavigation : visitorNavigation)(user);
Enter fullscreen mode Exit fullscreen mode

From your point of view it might look like "the same", but now we not only made navigation way easier to read, but also made its parts reusable (consider also that generally we wouldn't have all the functions in the same file). The if version, for contrast:

// The convoluted approach
const navigation = user => {
    if (user.loggedIn) {
        if (user.canMod) {
            if (user.isAdmin) {
                return adminNavigation(user);
            } else {
                return modNavigation(user);
            }
        } else {
            return userNavigation(user);
        }
    } else {
        return visitorNavigation(user);
    }
};

// The early return approach
const navigation = user => {
    if (user.loggedIn) {
        if (user.canMod) {
            if (user.isAdmin) {
                return adminNavigation(user);
            }
            return modNavigation(user);
        }
        return userNavigation(user);
    }
    return visitorNavigation(user);
};

// The split into multiple functions with if approach
const canModNavigation = user => {
    if (user.isAdmin) {
        return adminNavigation(user);
    }
    return modNavigation(user);
};

const loggedInNavigation = user => {
    if (user.canMod) {
        return canModNavigation(user);
    }
    return userNavigation(user);
};

const navigation = user => {
    if (user.loggedIn) {
        return loggedInNavigation(user);
    }
    return visitorNavigation(user);
};
Enter fullscreen mode Exit fullscreen mode

You can see that is very "boilerplaty" and harder to read, to achieve pretty much the same thing. It isn't about "I like how it looks".

Why should you state "undefined" just because foo is falsy?

You read that wrong, read it as if it was inside a function:

// When you write this:
const hello = user => {
    if (user.isAdmin) {
        return "Hello, admin!";
    }
};

// If you call it with an admin it runs ok
hello({ isAdmin: true }); // "Hello, admin!"

// But when you call it with a regular user...
hello({ isAdmin: false }); // undefined
Enter fullscreen mode Exit fullscreen mode

This might be intentional, maybe you actually want to return undefined, but the problem is that that's implicit, not explicit. When you do it with ternaries, you have to be explicit about what you want:

// If the `undefined` output is intentional, then...
const hello = user => (user.isAdmin ? "Hello, admin!" : undefined);

// If the `undefined` output was an accident, we can't avoid writing the "else" branch:
const hello = user => (user.isAdmin ? "Hello, admin!" : "Hello, user!");
Enter fullscreen mode Exit fullscreen mode

And again, just for comparison, the same "fix" with if:

const hello = user => {
    if (user.isAdmin) {
        return "Hello, admin!";
    }
    return "Hello, user!";
};
Enter fullscreen mode Exit fullscreen mode

But obviously, the final return is not "mandatory", you can forget about it (you can enforce it with linters, but is not enforced by the language itself). Hope this makes it more clear for you.

Cheers!

PS: Next time, try to avoid saying things like "extreme lack of knowledge", because it's kinda rude.

Thread Thread
joelbonetr profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
JoelBonetR • Edited on

You are assuming that conditionals are equal to functions and it could be or it could be not. You are setting up examples that fit your thoughts but did you try to find examples where they does not?

I'm nothing wrong with this way of working, with any way of working btw. I've an issue with generic things like "Do this always like this", "STOP using ###", "you don't need ###", "use ### trendy tech in every single project".

There are hundreds of thousands here that are self taught devs in progress, newbies, sometimes without any official education base (specially in this platform in case you didn't realize). This added to the language barrier, clickbait and shitposting make it hard for all this people to learn the things properly.
Gosh, It's hard even to a senior engineer with college degree and master to find proper information here about certain subjects!

Do you had a good education? Perfect, so did I and thus we've a responsibility to communicate properly. Didn't you? no problem, search for information in a given topic, teach yourself and then write an objective and sincere post.

I work as tech lead, close to every single person that come to the projects I work on and I'm sincerely tired of code reviewing, teaching and refactoring things that were done in a single way because "this is supposed to be the way I do the things", without analysis, without thinking. Trust me when I say that this blind following hurts more the industry than a random guy not splitting some logic into a couple of functions.

Some put an if else instead a ternary? I just don't give a f***, if it's correct, covers the use-cases and it does not qualify to produce future bugs is ok to me. Getting your code into production and a comment like "YOU did the right thing, you're in the good way, keep it on" is a boost for people. Stopping PRs just because "you could have done that with a ternary instead" is upsetting and a non sense most of the time.

On the other hand if we're in a process where performance concerns are in the acceptance criteria and you use a map, I'll not approve this PR for obvious reasons and I'll explain them so it will need to be refactored with a for loop instead. Is a map more readable? yes it is, but in performance terms is unacceptable.

Now, to follow the thread:
If you have to show or hide a specific menu item when the user is logged, is ok to have the isLogged() : boolean function to reuse but would you separate the shouldThisItemBeShown() in a different one and then having to chain functions? Regarding that:

  • It's a specific use case, no need to reuse (now, we can refactor in a future if needed).
  • The function will only be present along with the component containing it or providing it on a different file (you need to choose)
    • If you provide this function along this component you'll need to load the entire component just for that, which makes it not so reusable.
    • If you provide every single function for silly conditional checks as single file the project itself will be a mess in maintainability terms in few weeks.
    • If you provide it in a utility file flooded with functions like that you'll be importing (probably in the view global scope) all of them either they are used or not.
  • Last but not least, a conditional is a conditional and a function is a function. Sometimes a set of conditionals and actions can qualify as functions and sometimes it can be worse.

I'll rather do something like:

export default function Nav() {
  return (<nav>
      // logo
      // links list
     isLogged() ? <Link bla bla /> : '';
  </nav>);
}
Enter fullscreen mode Exit fullscreen mode

The issue with all that subject is that you're right from the beginning.
It's just that your posts and answers are not right always, just when they fit. You need to be more flexible and discern whether is better to walk the way of separate the logic or keeping it dependant on it's context and it will depend on each use case.

You assume the use cases that fits what you like most (like a politician) and IT is a science, there's no single thing to rule them all.

You even literally said as a pro for a ternary that "It forces to cover both if and else case."

Collapse
haseeb1009 profile image
Abdul Haseeb Author

Very well explained @lukeshiru
I think moving nested conditions away make it more explicit and readable and yet compact than multiple if else

Collapse
aschwin profile image
Aschwin Wesselius

Thanks for the examples in JavaScript.

I thought you were going to show the use of monads in JavaScript, LOL. Because every time I see a post about omitting if/else, switches etc. they introduce the usage of monads. Which are maybe better, computation and logic wise, but a hard topic to master.

Collapse
totally_chase profile image
Phantz

The irony there being, to implement monadic binding to abstract away branching, you need branching in the first place :)

Since we did bring up monads, the entirety of category theory must now follow. Selective applicative functors showcase the "branch"ing effect abstracted away into the functor. Which is ofcourse a realization of strength and costrength applied elegantly. The application itself, of this abstract concept in practice, requires laziness, but that's uninteresting.

I kid, I kid. Not "kid" in the sense that everything I said above is nonsense (it isn't), but rather, in the sense that all of it should be nonsense :)

(Haskellers, please don't kill me with your arrows)
(Non-Haskellers, please don't crucify me for unnecessarily bringing up category theory)

Collapse
gauravchandra profile image
Gaurav Chandra

Although this is fine for concise code but it is a nightmare when it comes to readibility. Not every dev is on the same level and it is imperitive that the code be readable. With all these bundlers in js ecosystem, this code will anyways be coompacted.

We should not forget that right now humans are writing the code and not machines so humans need to understand that part. The code anyways boils down to machine code at the time of compilation/transpilation. And I completely agree with @joelbonetr .

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
haseeb1009 profile image
Abdul Haseeb Author

yeah that was something opinionated as I mentioned and any thoughts will be appreciated.
Thanks for highlighting the typo :)

Collapse
barakplasma profile image
Michael Salaverry

I was hoping for an explanation of polymorphism in this article to replace if/else
See refactoring.guru/replace-condition... for more details on it