DEV Community

Cover image for 🚫 Stop using SWITCH, please πŸ™

🚫 Stop using SWITCH, please πŸ™

Ivan Zaldivar on April 04, 2023

If you're a software developer, chances are you'll use the switch stament at some point. This operator allows you to evaluate the expression and ex...
Collapse
 
danielepecora profile image
Daniele Pecora • Edited

Imho this article is just creating a problem out of absolute nothing.
A switch statement is totally fine to use until it reaches a distinct amount of switch cases.
After that a refactoring would be necessary to maintain readability.
This would also be the case whe using if statements.
So just clickbait?

Here the original article source:
dev.to/tauantcamargo/replace-your-...

Collapse
 
wizdomtek profile image
Christopher Glikpo ⭐

The decision to use or avoid switch statements depends on the specific situation and the developer's preferences and coding standards.Thanks for sharing

Collapse
 
ivanzm123 profile image
Ivan Zaldivar

Totally agree, sometimes we just need guard clauses to make the code more readable.

Collapse
 
zirkelc profile image
Chris Cook • Edited

In my personal opinion, the switch-case block in the first code block is perfectly fine and in my opinion also better than the presented alternatives. It is much more readable due to less code and probably also faster and more efficient than the other solutions.

The if-else option has to evaluate the condition three times in the worst case, instead of once. Not a big deal, to be honest, but if we are discussing such a topic, it needs to be mentioned.

The key-value option (object or map) contains much more boilerplate code, which makes it harder to read, understand, and maintain. It also causes memory overhead due to additional objects (map, functions, etc.).


That being said, I would extract the three notification functions into three separate functions and call them from either the switch block or the if block. No need for overengineering.

It certainly makes sense to avoid switch blocks in certain cases, especially if they span long lines of code. However, this is not such a case. And if the post title has such a strong opinion about switch-blocks, it should also present a strong use case against them.

Collapse
 
aarone4 profile image
Aaron Reese

But there is a tipping point. The example code is short for article readability. If there were 20, 200 or 2000 notifications the switch pattern is unmanageable (and untestable). However if you start with the switch pattern, the cost to refactor can be too great to fit into a development sprint and you end up with tech debt

Collapse
 
heinric14597733 profile image
Heinrick

Please, think about it. That's nonsens. I don't believe, you had such this situation in any of your sources. Not to manags and no way to test it? What? I use switch-blocks since over 40 years. And i would never use an if-then-else-basic-shit. Sorry. This article is redudant.

Thread Thread
 
aarone4 profile image
Aaron Reese

canonical example. medical SNOMED/HL7 codes. There are literally thousands of them... It is likely that a solution starts out for one particular medical diclipline and as the software expands, you need to do different things. In the end you need to be able to map an enumerated value to an action. Whether you do this with SWITCH, IF-ELSE, object keys or a database is an implementation detail.

Thread Thread
 
emi_black_ace profile image
Jacob Van Wagoner

You wouldn't be calling a different action for those codes, you'd be calling the same action with different parameters.

A more realistic case would be a device controller in which what you do depends on how the device answers back. Even then usually it's enough for a switch statement but some devices can be hella complex in their interfaces.

Collapse
 
zirkelc profile image
Chris Cook

How would the condition to check which type of notification to send be any different? The notification type will be determined by either a) switch-statement, b) an if-statement or c) the key-value-map.

In all three cases you should most likely extract the actual notification logic into its separate function, depending on the length and complexity. But that has nothing to do with switch-statement in particular. It equally applies to all three use cases.

Collapse
 
suobig profile image
Popov Anton • Edited

If you use TypeScript, you don't need to refactor your Switch statements. You can make them safer with this trick:

type Color = 'red' | 'blue' | 'green'

export function preventSwitchDefault(_: never) {
  throw Error('this code should not be reached')
}

export function testColorSwitch(color: Color) {
  switch(color) {
    case 'red':
      return 'color is red'
    case 'blue':
      return 'color is blue'
    default:
      preventSwitchDefault(color)
  }
}
Enter fullscreen mode Exit fullscreen mode

When you use type never TypeScript explicitly checks that default case is unreachable and shows you an error if it isn't.

Collapse
 
salcrio profile image
Sergio

mejor usar un enum

Collapse
 
neeh profile image
neeh • Edited

Strongly disagree.
1) I don't think switches are less readable when you are used to them. They don't come up very often in code because they are only useful in specific cases so they might look "odd" to the inexperienced eye.
2) You haven't mentioned that switches are powerful when mutliple cases lead to the same branch.
3) Switch cases are NOT tested sequentially like a chain of if {} else if {}. Try to write a little piece of C code with a switch, compile it, look at the generated Assembly result and it might surprise you how good compilers are at optimising switches.
4) When it comes to performance, using a string as a case value is already a bad idea. A properly designed software should use enums for that anyway (in JS too). Besides, hashmaps are dynamic runtime structures, so they are not as predictable and therefore not as optimisable as the code branches of a switch, and that's true for any language.

The only thing I would agree on is that switches are often used to patch bad design, which might also be why they have a bad reputation. If I have a lot of branches in a switch, I would try to refactor the code using polymorphism.

Collapse
 
joshuakb2 profile image
Joshua Baker

Switches in JS are indeed sequential tests. Unless there are some JIT optimizations I'm not aware of, each case of a switch is tested in order until one matches.

Collapse
 
neeh profile image
neeh • Edited

I just had a quick look at the V8 source and it turns out that some switches are already optimisable by the JIT compiler before any heavy optimization work by TurboFan.

Specifically, any switch that meets the following conditions will get optimised with a jump table:

  • uses small integers as case values (Smi = 31 bits)
  • has at least 6 cases
  • has a spread (difference between the max and min case values) that is at most 3 times larger than the number of cases

Here's the source code for that:
src/interpreter/bytecode-generator.cc
src/flags/flag-definitions.h

Note that the jump table will be tested first. If the switch has any non Smi cases, they will be tested AFTER any case available in the jump table, even if they appear before some of these cases in the code.

Also note that this is just a basic compilation optimisation. When the function becomes "hot" it will be picked up by the optimiser (TurboFan in V8). And the optimisation performed will be much more important than a simple jump table. Some of function calls inside the cases may even be inlined inside the parent function. It's much harder (though not impossible) for the optimiser to optimise if you use a hashmap because a hashmap is dynamic so the optimiser has to make assumption before inlining and verify that these assumptions are still true when calling the function.

Thread Thread
 
asafalroy profile image
Asaf Alroy

I literally created an account with the sole intention of asking you how the **** did you come up with this code? Like, really, how do you know your way around this repo so good to find this? Or if not how did you manage to do it anyway? This is an invaluable skill imo

Thread Thread
 
neeh profile image
neeh

Thanks asaf!
I'm not familiar with the v8 source code but I could navigate it rather easily because:

  • I have a rough idea of what I will find in the code: I know that there is an interpreter that converts JS to bytecode and an optimizer that optimizes this bytecode and I know that they must both be isolated from the rest of the code to limit the complexity of such a large codebase
  • The code uses predictable names for folders/files/functions/variables like "interpreter", "bytecode-generator.cc", "IsSwitchOptimizable", "IsSpreadAcceptable". It's almost impossible to find better names. Besides, the developers were generous enough to leave readable comments to clear any confusion that may arise when reading the code
  • I cloned the repo locally on a SSD so I could do folder-wide searches quickly

Beyond that, it's a matter of opening and checking folders/files and guessing if you're on the right track. Also I have only dug into the interpreter code, which is the easy part I suppose.

Collapse
 
neeh profile image
neeh

All JS JIT compilers come with optimisers that will sort this out (e.g. V8's TurboFan).

Collapse
 
brense profile image
Rense Bakker

Partially agree with you, although unsure about the proposed solution... In most cases I would just encourage people to use guard clauses, with clear descriptions like:

const isUserSigningIn = true

if(isUserSigningIn){
  // Do sign-in stuff
  return
}

// Etc...
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ivanzm123 profile image
Ivan Zaldivar

The use of Objects or Mapping Functions depends on the context of what is being integrated. It is up to the developer if it is useful or not. Thank you very much for your comment. 🀜

Collapse
 
jamesvanderpump profile image
James Vanderpump

I would take a switch statement over the provided alternatives any day. We need to keep readability in mind. When you return to that specific section in three years, you want to see at first glance what's happening.

Collapse
 
pengeszikra profile image
Peter Vivo

In react I using switch-return for useReducer, I think that way much simplier, than create lot of extra function.

for example:

export const gameReducer = (state:MainState, {type, payload}):MainState => {
  switch (type) {
    case SET_HERO: return {...state, hero: payload};
    case MOD_HERO: return {...state, hero: payload(state.hero), combatResult:null};
    case NEXT_ROUND: return {...state, round: payload(state.round)};
    case SET_GAME_STATE: return {...state, game:payload};
    case SETUP_ENTITIES: return {...state, entities: payload};
    case SET_MOB_LIST: return {...state, mobList: payload};
    case MOD_ENTITI: {
      const {entities} = state;
      return entities[payload?.uid] 
        ? {...state, entities: {...entities, [payload?.uid]: payload} }
        : state;
    };
    case FOCUS_ON: return {...state, focus: payload};
    case PLAY_ACTION_ANIM: return {...state, actionAnim: payload};
    case PLAY_FLOW: return {...state, flow: payload};
    case ENCOUNTER_BEGIN: return {...state, encounterResult: null};
    case ENCOUNTER_RESULT: return {...state, encounterResult: payload};
    case SET_AUTO_FIGHT: return {...state, isAutoFight: payload};

    case LEVEL_UP_HERO: return {...state, hero: increaseLevel(1)(state.hero)}
    default: return state;
  }
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
brense profile image
Rense Bakker

You don't need a switch at all for such a reducer, this is plenty:

function simpleReducer (prevState, nextState){
  return { ...prevState, ...nextState }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
pengeszikra profile image
Peter Vivo

that is only complicated the whole reducer, because in this case - real exmaple - you can call this "simpleReducer" from each line.

Switch version do not need to create 14 different reducer function call - one calls level less solution. Check this solution, in each case you declare which state property chagened. Plus as you saw this gameReducer have return type definition ( at once ), that guard each of them.

Thread Thread
 
brense profile image
Rense Bakker

Nope, no need to declare separate functions

const initialState = {
  user: { name: "Someone" },
  product: { title: "Something" }
}

function simpleReducer(prevState: typeof initialState, nextState: Partial<typeof initialState>){
  return { ...prevState, ...nextState }
}

function SomeComponent(){
  const [{ user, product }, setState] = useReducer(simpleReducer, initialState)

  const updateOneProperty = useCallback(() => {
    setState({ user: { name: "Someone else" }})
  }, [])

  const updateAll = useCallback(() => {
    setState({ user: { name: "Foobar" }, product: { title: "Hello world!" }})
  }, [])

  return <>
    <span>{user.name} - {product.title}</span>
    <button onClick={updateOneProperty}>Update one property</button>
    <button onClick={updateAll}>Update all</button>
  </>
}
Enter fullscreen mode Exit fullscreen mode

You only need advanced reducers if you have to manage arrays, like adding, updating or deleting items to/from array.

Collapse
 
bobbyconnolly profile image
Bobby Connolly

Have a look at immer. It might help you here

Collapse
 
llorx profile image
Jorge Fuentes

All this hassle can be avoided just by adding a "default:" with an error throw. If you forget to add the new fruit to the switch, it will throw an error exactly the same as your object solution.

Also, if the input comes from "outside" (not fully under your control), your object solution is bugged. Use "toString" as the notification name and see. Trying to find a safer solution you just created an unsafer one. Sometimes overengineering is a problem more than a solution.

Collapse
 
facum profile image
Facundo Montero • Edited

And a default case is not even needed, check this example:

export function notifier (notification: Notification) {
  switch (notification) {
    case Notification.verifySignUp: 
      // Execute something...
    return true;

    case Notification.resendVerifySignup: 
      // Execute something...
    return true;

    case Notification.emailConfirmed: 
      // Execute something...
    return true;
  }

  return false;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
husky931 profile image
Husky

they kinda seem harder to read

Collapse
 
ant_f_dev profile image
Anthony Fung

I agree. I've used this pattern before, but with some refactoring.

const verifySignup = () => {
  // ...
};

const resendVerifySignup = () => {
  // ...
};

const emailConfirmed = () => {
  // ...
};

const notificationMap = new Map<string, () => void>([
  ['verifySignup', verifySignup],
  ['resendVerifySignup', resendVerifySignup],
  ['emailConfirmed', emailConfirmed]
]);
Enter fullscreen mode Exit fullscreen mode

I find this helps to keep everything in one place while separating them too.

Collapse
 
zreese profile image
Zachary Reese

Easier to maintain: As new notifications are added, there is no need to add more instances inside a switch statement, just add a new function to the notificationDirectory object.

What? How is one of these things easier than the other?

Collapse
 
realratchet profile image
Ratchet

Congratulations you just replaced performant code for code that YOU subjectively think looks better with a GC overhead. Sure you can do it but none of the so called benefits hold true.

You may think that a hash map is O(1) but it's not it comes with a huge overhead even if you ignore the fact that you just created VM functions.

Unless your use case involves hundreds of return paths linear search will ALWAYS be faster. In both cases you can optimize based on code execution rank but switch case may or may not be optimized for by JIT depending on implemention.

So please stop posting about micro-optimizations.

Collapse
 
cmcnicholas profile image
Craig McNicholas

Agree with the article unless you can guarantee a finite number of cases over time.

In almost all other cases a map keyed to your execution function is more readable, scalable, testable and importantly most people forget, much more commit friendly. I've had plenty of collisions on active teams with switch statements that never happened (or are much simpler to resolve) when simply populating a map.

Collapse
 
sollyucko profile image
Solomon Ucko

Can you give a specific real or hypothetical example of the same collision with each?

Collapse
 
smith76432 profile image
smith76432

Apologies for the previous use of the word "switch." I'll avoid using that term moving forward. If you have any specific requests of Facebook owner game or questions, please let me know, and I'll be happy to assist you.

Collapse
 
emi_black_ace profile image
Jacob Van Wagoner

This is terrible advice.

If you have known and few cases, keep using Switch. It's the easiest and most readable, and the way it's implemented is usually very fast.

If you have many and unknown-at-release-time (i.e. you're shipping something that supports plugins) then use polymorphism -- that is, you pass an object of the base type and call an abstract/virtual function and you let the derived types define that function. This is also fast as heck as it's a vtable lookup, it's understandable, it's maintainable and expandable.

The mapping function is what you do if you need to expand the functionality AT RUNTIME ONLY. It's much harder to read and maintain otherwise.

Stop posting clickbait titles.

Collapse
 
adairo profile image
adairo

Yeah sometimes it would be nicer to extract an object with the operations. But one of the problems that I have faced is that you no longer can use a closure to access the data where the original switch was. I can't think of a way to achieve the same result when the switch was inside a class, accessing private members to carry out the result.

Collapse
 
kmo profile image
kmo

This is to be honest a very complicated solution for a made up problem. If your switch case, or in newly Java Versions your switch expression is getting too big, then you might think of using maps, lambdas or if else constructs.

Collapse
 
pandeleflorin profile image
PandeleFlorin

The switch is far more preferable to endless if else if else chained statement s . Yes it is preferable to do a mapping for a large number of branches but that happens extremely rarely. There's no point in doing mappings or derived classes if you can solve that with a 5 branch switch. But that is the core of being a good programmer: choose the appropriate tool for the appropriate situation. And not typing 100 classes per minute. Think strategically and act carefully.

Collapse
 
ironsavior profile image
Erik Elmore

That's gonna be a "no" from me, dawg. Switch isn't the best for everything, but it's the most appropriate option virtually anywhere you have more than one mutually exclusive branch.

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Switch statements absolutely have great usecases

Objects with functions as propvalues are much less readable.

Collapse
 
dbrasdasilva profile image
Darcy Bras da Silva

I am sorry but the switch statement has the opportunity to be in the stack and to still return early and now we want to use heap allocated memory for the hash map to get the same result… over engineering at best to not solve a real problem

Collapse
 
szeredaiakos profile image
szeredaiakos

Well, if you need to modify 2 code blocks that always comes with some dangers. It is not a switch statement problem, but a more underlying issue.

It is rare when developers can put their fingers on problems like this and they'll go on and blame a certain pattern, paradigm, technology or in this case, a statement.

I found modeling diagrams can help alot to find out the ways a particular piece of code can change. The understanding of clients and development roadmaps define how it is most likely to change.

Collapse
 
icolomina profile image
Nacho Colomina Torregrosa

I think there is no problem using switch or match when the number of cases are not many. If they are growing so much then it'll be necessary to refactor.

Collapse
 
kristoferjoseph profile image
kj

I had some similar feelings about switch in the past.

I ended up making this
github.com/kristoferjoseph/hash-sw...

Collapse
 
facum profile image
Facundo Montero

Or if it hits the default case just throw an exception?

Collapse
 
r4e profile image
Richie Permana

Safer? Lol, what safe?

Let me introduce you, the "default" argument for switch statement.

Collapse
 
beggars profile image
Dwayne Charrington

Stop using switch?

No. I don't think I will.

Collapse
 
powercreek profile image
PowerCreek

Wow, you just leaned into an antipattern. Im amazed

Collapse
 
gozzy82 profile image
Gozzy82

With the multiple if statements I think the switch(true){
case notification === Notification.emailConfirmed:
// do something
Break;
}

Can also be used.