DEV Community

Cover image for βœ”||🀒 Commit or Vomit | Switch(true)

βœ”||🀒 Commit or Vomit | Switch(true)

🐀πŸ₯‡ Jasper de Jager on April 02, 2021

In my recent post I questioned the use of a switch instead of an if else statement. This gave me the idea of an recurring item for dev.to: Commit o...
Collapse
Β 
siddharthshyniben profile image
Siddharth β€’ β€’ Edited

This could easily have been an if/elseif/else.

You could have done:

if(someExpressionA) console.log('yes')
else if ((someExpressionB 
    && someExpressionC) || someExpressionD) console.log('nope');
else console.log('maybe');
Enter fullscreen mode Exit fullscreen mode
Collapse
Β 
basbenik profile image
Bas van Baalen β€’

I'm not sure it's because of the editor, but I would suggest to use more lines for more readability and easier comprehension.

if(someExpressionA) 
  console.log('yes')
else if ((someExpressionB && someExpressionC) || someExpressionD) 
  console.log('nope');
else 
  console.log('maybe');
Enter fullscreen mode Exit fullscreen mode

For me this already reduces the cognitive load a lot quickly see what can happen.

Collapse
Β 
siddharthshyniben profile image
Siddharth β€’

Actually I typed my comment on mobile, so I didn't format it

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Good point for a next Commit or Vomit! What do you think about switch(true) in general?

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

true, this is a short example. Apart from this example would you never commit a switch(true)?

Collapse
Β 
siddharthshyniben profile image
Siddharth β€’ β€’ Edited

Nope 🀒

Collapse
Β 
jackmellis profile image
Jack β€’

Since you're returning on each case I wouldn't even bother with elses...

if (useMissedAppointment) {
  return 'nope
}
if (userHasAngularExperience || useHasReactExperience || (userHasVieExperience && userCanStartImmediately)) {
  return 'hire'
}
return 'maybe'
Enter fullscreen mode Exit fullscreen mode

Of course I'd also extract that second conditon into another method, but that's another matter.

Collapse
Β 
natalia_asteria profile image
Natalia Asteria β€’ β€’ Edited

Um, making it a bit inline is better imo.

if (userMissedAppointment) return 'nope';

if (userHasAngularExperience || useHasReactExperience || 
(userHasVieExperience && userCanStartImmediately)) return 'hire';

return 'maybe';
Enter fullscreen mode Exit fullscreen mode

The first line for the second if statement is too long imo. Turned it into two lines.

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’ β€’ Edited

My preference:

if (userMissedAppointment) return 'nope';

if (
  userHasAngularExperience 
  || useHasReactExperience 
  || (userHasVueExperience && userCanStartImmediately)
) return 'hire';

return 'maybe';
Enter fullscreen mode Exit fullscreen mode
Thread Thread
Β 
natalia_asteria profile image
Natalia Asteria β€’

Oh yeah, I didn't thought that.

Collapse
Β 
jackmellis profile image
Jack β€’ β€’ Edited

Personally I prefer all-or-nothing for braces, I really don't like mixing styles. If any of my code has braces, I'd prefer all of my code to have braces. But this is totally off topic and probably a topic for another C/V poll!

Thread Thread
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

I'll save it for another βœ”οΈ||🀒 good idea.
Just have to come up with a good example 😊 and not posting a new one every day is hard but I think this is going to be a weekly recurring item from now on 😎

Collapse
Β 
asdftd profile image
Milebroke β€’ β€’ Edited

I think the cleanest and most expressive is still this


const hasFrontendFrameworkExperience = userHasAngularExperience || userHasReactExperience || userHasVueExperience;

if(userMissedAppointment){
    return 'nope';
} else if(hasFrontendFrameworkExperience && userCanStartInstantly){
    return 'hire';
}
return 'maybe';
Enter fullscreen mode Exit fullscreen mode
Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Your function behaves slightly different than the example, bit this just might prove your point of the switch not being readable enough πŸ˜…

The difference is that in the switch an angular experienced used doesn't have to start immediately.

Collapse
Β 
asdftd profile image
Milebroke β€’

You are definitely right :D Well in that case I wouldn't mind the right if else combo either

Collapse
Β 
indoor_keith profile image
Keith Charles β€’

While the code is valid, I think the real issue here is just the amount of conditions you're trying to parse through. Using switch (true) feels more like a band-aid than a solution I would accept say in a PR.

I'm a fan for using switches when we're actually comparing the value in switch(value). Someone mentioned checking for a match in the zodiac. Perfect use case for a switch statement.

When you have to resort to a hacky solution, chances are the problem lies more with the code leading up to this decision than the decision itself.

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Totally agree! It's a code smell.

Collapse
Β 
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan β€’ β€’ Edited

Why the need for the case statement?

if (userMissedAppointment) {
  return 'nope';
}
const userMeetsTechRequirements = userHasAngularExperience || userHasReactExperience || userHasVueExperience;
if (userMeetsTechRequirements && userCanStartInstantly) {
   return 'hire';
}
return 'maybe'
Enter fullscreen mode Exit fullscreen mode

Also, imo, there's no need to repeat "user" in these flags. You could just say canStartInstantly, for example. There's no ambiguity regarding who this refers to.

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Totally agree, but would you merge it if it was in a PR for instance?

Collapse
Β 
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan β€’

Nope, I'd request changes here.

Collapse
Β 
disgustingdev profile image
disgusting-dev β€’ β€’ Edited

How about that (pre-enterprise example :))

//allocate in one place binding of all rules with results
const conditionsEnum = {
  nope: ignoringConditions,
  hire: hiringConditions,
  maybe: mbConditions,
}

let result = '';

//find first truth in there
for (let field in conditionsEnum) {
  if (conditionsEnum[field].some(condition => !!condition())) {
    result = field;

    return result;
  }
}
Enter fullscreen mode Exit fullscreen mode

values in object are just arrays of functions:

const ignoringConditions = [
  () => false,
  () => false,
  () => false,
]

const hiringConditions = [
  () => true,
  () => false,
  () => false,
]

const mbConditions = [
  () => false,
  () => false,
  () => false,
]
Enter fullscreen mode Exit fullscreen mode

Hope my nickname speaks for itself

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Hope my nickname speaks for itself

🀣🀣

Collapse
Β 
niorad profile image
Antonio Radovcic β€’

I'd say probably vomit, but I'm sure there are cases where this way is just easier to read than if/else/return early/etc. Hard to say with placeholder-var-names.

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Yes I agree, something to take into account for the next one in the series!
Thanks for the feedback 😊

Collapse
Β 
niorad profile image
Antonio Radovcic β€’

switch(true){
case userDoesntHaveWorkPermit:
console.log('nope');
break;
case userHasReactExperience:
console.log('Hire!');
break;
case userHasVueExperience
&& userCanStartInstantly:
console.log("Hire, if they can start instantly");
break;
default:
console.log('maybe');
}

Thread Thread
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

More of an Angular fan myself so userHasAngularExperience is going to be added 😎 but much better example, thnx!

Thread Thread
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Snippet was updated!

Collapse
Β 
siddharthshyniben profile image
Siddharth β€’

I can't believe some people put hearts

Collapse
Β 
cariehl profile image
Cooper Riehl β€’

I liked this post, not because I think switch (true) is a good paradigm (it isn't), but because the post itself is interesting.

The author isn't saying "you should use switch (true) in your code", they're saying "let's have a discussion about whether switch (true) is acceptable". IMO, that's a useful discussion!

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

That's the fun of this item 😊
It shows it's important to keep an open mind!

Collapse
Β 
sirnino profile image
Antonino Sirchia β€’

Honestly you made me really curious about the performance... The switch normally are the first choice to avoid writing long if-else chains because the code "jumps" directly to the right place without evaluating all the other conditions... I'm curious to understand if also in this case these considerations are valid or not... I'll try and, if so, it will be an heart

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

I don't expect any performance gains but I'm looking forward to your findings!

Collapse
Β 
andreidascalu profile image
Andrei Dascalu β€’

Vomit.
Either a verbose aggregation of desired Boolean checks OR return from currying some composable functions that return a similar aggregation.
I am slightly partial to the second as I believe anyone looking for FP would be.

Collapse
Β 
serializator profile image
Julian β€’

I think this is a misuse of the switch statement and its purpose.

If there is a need for this kind of hackery witchery I believe there are other design flaws which makes this code "necessary" and should be refactored to make this kind of code avoidable.

When an if statement gets so complex that you start to search for more readable ways of writing it there will most likely also be alternative ways to take it apart into smaller parts and make it more comprehensible (and maintainable) that way.

Collapse
Β 
peerreynders profile image
peerreynders β€’ β€’ Edited

What do you think about switch(true) in general?

πŸ‘Ž

With that out of the way I do think it's worth hypothesising how code like this may have come in to being.

But even before that I'm surprised nobody claimed primitive obsession given that there are five distinct boolean values on the "loose".

Seems the more likely scenario should be something like:

const results = {
  noShow: false,
  immediateAvailability: true,
  experience: {
    angular: false,
    react: false,
    vue: true,
  },
};
Enter fullscreen mode Exit fullscreen mode

With that reorganization my next guess should make more sense. I suspect the switch(true) is a degenerate form of some copy-pasted code that was trying to approximate pattern matching.

While a number of languages support pattern matching, JavaScript only supports destructuring assignment β€” not pattern matching:

The big difference is simple: Pattern matching is a conditional construct and destructuring isn’t.

There is a TC 39 proposal to add pattern matching to JavaScript (which hasn't moved beyond stage 1 since 2018). Meanwhile there are some third party libraries available that approximate some pattern matching functionality.

Using Pattern Matching:

import { when, _ } from 'pattern-matching-js';
// https://github.com/klo112358/pattern-matching-js

const STATUS = {
  reject: 'nope',
  accept: 'hire',
  reevaluate: 'maybe'
};

const results = {
  noShow: true,
  immediateAvailability: true,
  experience: {
    angular: false,
    react: true,
    vue: false
  }
};

/* eslint-disable no-unexpected-multiline */
// prettier-ignore
const status = when(results)
  ({ noShow: true }, STATUS.reject)
  ({ experience: { angular: true } }, STATUS.accept)
  ({ experience: { react: true } }, STATUS.accept)
  ({ experience: { vue: true },
     immediateAvailability: true }, STATUS.accept)
  (_, STATUS.reevaluate)
  ();

console.log(status);
Enter fullscreen mode Exit fullscreen mode

The actual proposal has much richer functionality that would allow for the aggregation of all the "accept" patterns. But if you squint, that when (or match) is reminiscent of a switch.

There is another way to represent boolean values - as bit flags.
Using bit flags/field operators:

const STATUS = {
  reject: 'nope',
  accept: 'hire',
  reevaluate: 'maybe',
};

const FLAGS = {
  noShow: 0x01,
  availableImmediately: 0x02,
  angular: 0x04,
  react: 0x08,
  vue: 0x10,
};

const results = FLAGS.availableImmediately | FLAGS.vue;
const status = interviewStatus(results);

console.log(status);

function interviewStatus(results) {
  if (match(results, FLAGS.noShow)) return STATUS.reject;

  if (
    match(results, FLAGS.angular) ||
    match(results, FLAGS.react) ||
    match(results, FLAGS.vue | FLAGS.availableImmediately)
  )
    return STATUS.accept;

  return STATUS.reevaluate;
}

function match(value, flags) {
  return (value & flags) === flags;
}
Enter fullscreen mode Exit fullscreen mode

Again interviewStatus() is somewhat reminiscent of a switch statement wrangled to behave like an expression.

One final point: readability.

It's kind of interesting how many comments claim that if based solutions are more readable. Personally I find that most of the if based proposals require a prolonged mental parse to just get the gist of the code. Obviously readability is a lot more subjective than most people are willing to admit.

Using the nullish coalscing operator:

const STATUS = {
  reject: 'nope',
  accept: 'hire',
  reevaluate: 'maybe',
};

const results = {
  noShow: false,
  immediateAvailability: false,
  experience: {
    angular: false,
    react: false,
    vue: true,
  },
};

const status = interviewStatus(results);

console.log(status);

function interviewStatus(results) {
  return maybeReject(results) ?? maybeAccept(results) ?? STATUS.reevaluate;
}

// "maybe" prefix - returns a value or `undefiend`
function maybeReject({ noShow }) {
  return noShow ? STATUS.reject : undefined;
}

function maybeAccept({
  experience: { angular, react, vue },
  immediateAvailability,
}) {
  return angular || react || (vue && immediateAvailability)
    ? STATUS.accept
    : undefined;
}
Enter fullscreen mode Exit fullscreen mode

In my view:

  • the rejection criteria can be easily located and identified quickly
  • the acceptance criteria can be easily located and identified fairly quickly
  • it should be obvious that rejection criteria take precedence over acceptance criteria

Now I'm certain there already is a queue of people forming getting ready to drop a ton of bricks on me, how they don't find the above code readable at all.

That's not surprising β€” something is only judged as "readable" if it is already familiar. Anything unfamilar to an individual is also not as readable because of the additional cognitive effort that is required to read it.

In natural langauge there is the notion that

If you learn only 800 of the most frequently-used lemmas in English, you'll be able to understand 75% of the language as it is spoken in normal life.

However

Eight hundred lemmas will help you speak a language in a day-to-day setting, but to understand dialogue in film or TV you'll need to know the 3,000 most common lemmas.

Going further

And if you want to get your head around the written word - so novels, newspapers, excellently-written … articles - you need to learn 8,000 to 9,000 lemmas.

So just because something isn't deemed "readable" by a certain group of people doesn't necessarily imply that it is unreasonable.

From that point of view it's important to get exposure to a broad spectrum of programming styles (preferably across languages from different programming paradigms) β€” with that exposure a lot more code becomes "readable".

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

This was a tough one for me, I like the style, but if/else is easier to read and comprehend. so for me it's a 🀒

Collapse
Β 
juanfrank77 profile image
Juan F Gonzalez β€’

I wouldn't personally commit a switch(true) or let anyone do it for that matter so I'll choose vomit.

Collapse
Β 
matjones profile image
Mat Jones β€’

switch (true) please stop making inexperienced JavaScript developers think this is a good idea 😱

Collapse
Β 
stevezieglerva profile image
Steve Ziegler β€’

You can always (and probably should) refactor long bool expressions into a functions with descriptive names.

Collapse
Β 
siddharthshyniben profile image
Siddharth β€’

I'm on mobile so I didn't really format anything

Thread Thread
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

It was sufficient to make your point 😊 no worries.

Collapse
Β 
edave64 profile image
edave64 β€’

This code definitely smells. So you have multiple conditions that are all mutually exclusive, but not similar enough that you can't just use a regular switch?

In any case, that's not what switch is for. If-else that stuff. Or, even better in this case, just if's because return ends control flow anyways.

Collapse
Β 
mahyargp profile image
Hossen Eki β€’

no is not readable

Collapse
Β 
siddharthshyniben profile image
Siddharth β€’

Looking forward to the next one.

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

I'd really like the discussion to be about the switch(true) and not about the code in comments. The code posted gives a good idea about why he voted vomit. It is not the code up for discussion.

Thread Thread
Β 
siddharthshyniben profile image
Siddharth β€’ β€’ Edited

You're right. Switching true just adds a few extra lines and makes us momentarily think "is it a typo? Why would you switch true". At least I did when I first saw a switch(true)

Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

Ok, just seamed off topic to me because of the style of the answer I guess. You said I" wouldn't commit your code" that's why.

Collapse
Β 
jmdejager profile image
🐀πŸ₯‡ Jasper de Jager β€’

and because this is the first:
Please leave comments if you see improvements for this item 😎