[HN Gopher] Using the switch(true) pattern in JavaScript
       ___________________________________________________________________
        
       Using the switch(true) pattern in JavaScript
        
       Author : seanbarry
       Score  : 97 points
       Date   : 2021-04-12 08:22 UTC (14 hours ago)
        
 (HTM) web link (seanbarry.dev)
 (TXT) w3m dump (seanbarry.dev)
        
       | elyseum wrote:
       | A good example of: just because you can, it doesn't mean you
       | should. Yes, the if/else sequence is a bit more unstructured in
       | terms of code layout, but chances the next developer browsing the
       | code will instantly know what it does will be much higher.
        
         | TeMPOraL wrote:
         | The only problem here is that you have to do this in the first
         | place. This problem has been solved some 60 years ago with Lisp
         | and `cond` construct, which is even older than `if/else
         | if/else`! Alas, our industry likes to forget history.
         | 
         | That said, this isn't some kind of high-level hackery. If it's
         | cleaner than if/else if chain, you should use it.
         | 
         | Programming is a profession. A professional is expected to
         | learn and grow. The right answer to seeing a code construct one
         | doesn't understand isn't throwing hands up in the air, but
         | spending the few minutes necessary to figure out what it does.
        
         | TheHalfDeafChef wrote:
         | I consider myself an advanced-beginner programmer untested in
         | the real world (looking for my first coding job though!), and I
         | understood it relatively quickly. It does appear more concise
         | than many if-else statements. After the initial "what?", I was
         | able to quickly parse through it. I think this is faster than
         | using the standard form.
         | 
         | [edit: fixed typo]
        
           | arielserafini wrote:
           | "faster" in which respect?
        
             | TheHalfDeafChef wrote:
             | Faster to parse as a developer. I can't speak to whether
             | computationally it is faster.
             | 
             | Additionally, doesn't this somewhat also bring in the
             | methodology of dealing with the error cases first?
        
           | [deleted]
        
           | t-writescode wrote:
           | A lot of the code you're going to have to be dealing with in
           | the wild is going to look fine, until it breaks at 2am on a
           | Saturday and you're 3 drinks in and production is down.
           | 
           | You're going to hate your former cleverness then.
        
             | TeMPOraL wrote:
             | There should be a rule that forbids applying this reasoning
             | to code that isn't triple-nested template SFINAE mess, or a
             | code-generating-code-generating-code-generating-code Lisp
             | macro.
             | 
             | This pattern isn't "cleverness", it's just a readability-
             | improving trick that's cheap to figure out. The hundred
             | seconds you spend on it seeing it for the first time is a
             | one-time cost.
        
             | tantalor wrote:
             | That's why I abstain when I'm oncall (or backup).
             | 
             | That's what the oncall bonus pay is for.
        
               | t-writescode wrote:
               | Replace alcohol with 2 hours of sleep, then :)
        
         | csmattryder wrote:
         | Spare a thought for the junior/mid-level devs who don't
         | understand clever patterns, and the seniors who haven't read
         | the same blog posts as the implementor of the switch(true).
         | 
         | Completely agree, it's like writing readable prose, understand
         | your audience.
         | 
         | That said, give me proper pattern matching in JS. Hard to live
         | with languages that haven't caught up yet.
        
           | nicoburns wrote:
           | > That said, give me proper pattern matching in JS. Hard to
           | live with languages that haven't caught up yet.
           | 
           | I really want this. Along with switch being as expression.
           | Seems like something that JavaScript is obviously missing.
        
             | jessaustin wrote:
             | _I really want... switch being as expression._
             | 
             | Unfortunately JavaScript still hasn't caught up with
             | CoffeeScript.
        
             | deergomoo wrote:
             | > Along with switch being as expression
             | 
             | Even PHP has this now, albeit with a different keyword
             | (match) due to existing switch semantics being pretty bad
        
       | harg wrote:
       | I think the post doesn't give a fair comparison as in the if/else
       | case there's no need to use "else" blocks if you're throwing an
       | error or returning. In this case I think simple "if" statements
       | are cleaner and certainly more "normal".
       | 
       | E.g.                 if (!user) {         throw new Error("User
       | must be defined.");       }             if (!user.firstName) {
       | throw new Error("User's first name must be defined");       }
       | return user;
        
         | CloselyChunky wrote:
         | When validation gets complex (e.g. there are many criteria to
         | check), I like to build a list/stream/array (what ever the
         | language offers) of tuples of predicates (functions from the
         | object that gets validated to boolean) and strings (or
         | functions from the object to string so I can have context in my
         | error messages).
         | 
         | Then iterate over the tuples, if a predicate fails, return the
         | associated error message and throw an error/display the message
         | to the user.
         | 
         | In the end it looks something like this:                 var
         | validators = Stream.of(         Map.entry(user -> user != null,
         | "User must be defined"),         Map.entry(user ->
         | user.firstName != null, "Missing first name"))
         | validators.filter(e -> e.getKey().apply(userToBeValidated)).map
         | (Map.Entry::getValue).getFirst()
         | 
         | (This example uses Map.entry for tuples as Java lacks native
         | support for tuples)
         | 
         | This limits branching and you have all validation criteria
         | neatly organized in the same location.
        
           | harg wrote:
           | Sure, if you're validating some data there're loads of better
           | ways to do it than a bunch of conditionals. I was purely
           | commenting on the switch(true) pattern vs some "if"s.
           | 
           | That approach looks nice though. On that subject, JS has some
           | nice libraries including io-ts[1] which has a functional
           | approach using Eithers to encapsulate errors/success.
           | 
           | [1]: https://github.com/gcanti/io-ts
        
             | CloselyChunky wrote:
             | Sure, if you have an either/result type, the whole thing
             | becomes a fold, where each validator is a function from
             | user to Either<Error, User> and then
             | validators.reduce(Either.right(user), (acc, next) ->
             | acc.flatMapRight(next))
             | 
             | This way you'll end up with either the validated user, or
             | the first error that occurred and all the other validators
             | were skipped.
        
           | wvenable wrote:
           | If you are using a language with yield you make this simpler
           | (and more flexible) by using a generator.
        
           | fl0wenol wrote:
           | Agreed, +1 This pattern goes beyond JS and is very helpful
           | for supporting tests; you've already factored out your
           | validators. There's always going to be stragglers but you can
           | define and push in an extra check on the spot (i.e. using
           | lambda funcs at least in JS).
        
         | _greim_ wrote:
         | I'm definitely in the minority here, but I'd rather see the
         | `else` block, since it's more explicit at-a-glance, and the
         | logic has more symmetry with all outcomes on the same level.
         | 
         | One reason I like RustLang is it treats this as an ergonomic
         | issue by appending `?` for early returns, without block-
         | nesting. So nice.
        
       | molszanski wrote:
       | In addition to an already mentioned early return pattern, I also
       | often recommend switching from switch to pattern matching via an
       | object.
       | 
       | It has an additional benefit of extracting code into data
       | structures or making them parametric                 function
       | getArrow(direction) {         switch (direction) {           case
       | "left":             return "<--"           case "righ":
       | return "-->"           default:             return "-\\_(tsu)_/-"
       | }       }            function getArrow(direction) {         const
       | arrows = {           left: "<--",           right: "-->",
       | default: "-\\_(tsu)_/-",         }         let arrow =
       | arrows[direction]              if (arrow) {           return
       | arrow         } else {           return arrow.default         }
       | }
        
       | graderjs wrote:
       | Guaranteed to always occur:                  ...        case
       | !isValidPhoneNumber(user.email):        ...
       | 
       | Tho see [1] and [2]
       | 
       | [1]: https://github.com/kdeldycke/awesome-falsehood#emails
       | 
       | [2]: https://github.com/kdeldycke/awesome-falsehood#phone-numbers
        
         | seanbarry wrote:
         | Careless typo - thanks for flagging ;)
        
         | dvaun wrote:
         | What a great set of resources. Thank you for sharing this repo!
        
       | 8128js8121 wrote:
       | ``` switch (true) { case 1 + 1 === 2: // This case evaluates to
       | true so it will be executed default: // This will not be executed
       | } ```
       | 
       | This is wrong, Since there is no return or break default will
       | also be executed.
        
       | dvirsky wrote:
       | Reminds me of a C++ pattern some guy I worked with years ago used
       | to love to simplify complex if checks - using do...while(false)
       | to create a scope you can easily break out of. e.g.
       | bool success = false;            do {         if (!someCondition)
       | break;         if (!otherCondition) break;          ...
       | success = true;       } while(false);              if (!success)
       | {        ...       }
       | 
       | I personally disliked it, plus it can lead to funky behavior
       | under optimization.
        
         | [deleted]
        
       | standardUser wrote:
       | On a similar topic, I'm wondering how often people are using the
       | "else" part of if/else these days. I haven't written "else" in
       | years and I've become very fond of that "if only" pattern.
        
         | alerighi wrote:
         | If you have a chain of conditions what you do?
         | if (condition1) {             // something         }
         | if (!condition1 && condition2) {             // other stuff
         | }          if (!condition1 || !condition2) {            //
         | finally         }
         | 
         | An if-else is more clear:                   if (condition1) {
         | // something         } else if (condition2) {            //
         | other stuff         } else {            // finally         }
         | 
         | To me if-else is more easy to reason about (since it's clear
         | that you enter in one of the 3 possible branches without even
         | looking at the conditions), but also it's more efficient,
         | especially if the condition is not a trivial comparison (for
         | example you are comparing strings, or doing some other linear
         | operation. And yes, computer are fast these days, but there are
         | no excuse for wasting resources for nothing to me).
        
           | TeMPOraL wrote:
           | If that if/else chain is returning a value, then what's even
           | more clear is:                 if (condition1) {
           | return something;       }        if (condition2) {
           | return otherStuff;       }       return finally;
           | 
           | I don't remember the last time I wrote an if/else chain that
           | wasn't in return position.
        
         | jbverschoor wrote:
         | Often enough, and I use if you capture paths that should never
         | happen logically.
        
       | kvczor wrote:
       | I prefer early return pattern.
       | 
       | Which would look like this:                   const user = {
       | firstName: "Sean",             lastName: "Barry",
       | email: "my.address@email.com",             number:
       | "00447123456789",         };              if (!user) {
       | throw new Error("User must be defined.");         }
       | if (!user.firstName) {             throw new Error("User's first
       | name must be defined");         }              if (typeof
       | user.firstName !== "string") {             throw new
       | Error("User's first name must be a string");         }
       | return user;
        
         | btown wrote:
         | I agree; `if (` is much more well-known and "grokkable" to
         | engineers at practically any level than `case` - and exactly
         | the same number of characters. There's no need to bring a
         | sledgehammer when the nail is perfectly handled by a normal
         | hammer.
        
           | layer8 wrote:
           | I agree, but the problem presumably is the "else".
        
         | cced wrote:
         | I think the issue here though, is that for certain cases such
         | as form field validation, you want all issues to be returned at
         | once. Using the switch method or similar message packages allow
         | you to inform your users that have N issues with a page in 1
         | request as opposed to N.
        
           | planb wrote:
           | That's not how throw works. And that's exactly why the
           | switch(true) pattern should not be used: novice users not
           | fully knowledgeable about every specification of a language
           | should not fail to understand such a basic piece of code.
           | 
           | There's not even a line difference to using if statements
           | correctly (as others in the comments have demonstrated) The
           | one thing an if statement does is checking if something is
           | ,true'. I don't understand why anyone would use a ,switch'
           | here apart from showing how clever they are.
        
           | Sayrus wrote:
           | Either you will break, thus exiting the switch-case block or
           | you will fall-through. The fall-through behavior won't do
           | what you want:
           | 
           | > the script will run from the case where the criterion is
           | met and will run the cases after that regardless if a
           | criterion was met.
           | 
           | Using the switch method won't allow you to return several
           | errors while the simple ifs method described earlier could
           | accumulate the errors and return later. The switch method is
           | elegant though.
        
         | thomas_moon wrote:
         | The early return pattern was the most effective single piece of
         | advice I received from a senior dev on how to make my code more
         | readable. You end up with clearer code paths than any other
         | pattern I have seen so far and way less indentation making
         | things look (and probably perceived) as less complex.
         | 
         | Pair it with naming your complex and chained expressions and
         | suddenly you have some seriously readable code.
         | 
         | So far, I have never seen a valid scenario where a switch
         | statement is actually any better than if.
        
           | [deleted]
        
           | mekster wrote:
           | Unwanted conditions should be handled before handling the
           | process in a peaceful condition.
           | 
           | Not sure it makes much sense to indent everything within "if"
           | and if you forget to "else", you've just potentially hidden a
           | bug.
        
           | anyonecancode wrote:
           | I think the early return pattern makes the most sense when
           | it's less "logic" and more "validation." Eg in the example,
           | the code isn't really _doing_ anything if there's no user or
           | no first name, it's running a bunch of checks _before_ trying
           | to do anything. Those validation checks are implemented with
           | if/early return, but the actual pattern is more "validate
           | first and only proceed if everything checks out."
           | 
           | In typed languages a lot of those checks get implemented in
           | your actual types, but you still might have various business
           | logic/data integrity checks you might implement in early
           | return.
           | 
           | Seen in this light, this pattern's really not so much in
           | tension with the idea of having a single return variable.
           | It's just a way to implement the idea that invalid states
           | should not be possible, which you accomplish in your type
           | system when and if possible, and fall back to runtime checks
           | for the gaps where it's not.
        
           | jandrese wrote:
           | Early return can get into trouble if you're talking about a
           | function that has a fair bit of state to unwind in the event
           | of an error. Open filehandles, allocated memory, general
           | cleanup stuff.
           | 
           | That said, I much prefer early return whenever it makes
           | sense. In functions that do have a lot to unwind and many
           | possible points of failure I'll pull out the old villain
           | 'goto' and have the unwind code at the bottom of the
           | function.
           | 
           | Strictly sticking to only one approach is usually a mistake.
           | One that is repeated a lot in computer science. There are
           | schools of thought that if everything is the same it will be
           | easier to understand, but what happens is problems that don't
           | exactly fit the mold end up being solved in awkward and
           | inefficient ways. Or development gets slowed because you have
           | to refactor your problem around the tools instead of the
           | other way around.
        
             | Jtsummers wrote:
             | > Early return can get into trouble if you're talking about
             | a function that has a fair bit of state to unwind in the
             | event of an error. Open filehandles, allocated memory,
             | general cleanup stuff.
             | 
             | This is where Lisp's _unwind-protect_ , _finally_ blocks in
             | some languages, _defer_ in Go, and C++ 's RAII pattern can
             | come in handy. Especially if you have a healthy respect for
             | goto and want to minimize its presence in your code for
             | various reasons.
        
               | golergka wrote:
               | `finally` introduces additional blocks, so if you have 5
               | different resources that you acquire as your function
               | goes along, you end up with 5 levels of curly braces. Not
               | very elegant.
               | 
               | `defer`, on the other hand, is probably the only thing I
               | wanted to take from Go to other languages I worked with.
               | Beautifully explicit and wonderfully useful.
        
           | brundolf wrote:
           | Unpopular opinion: I think early-returns make code less
           | readable if you don't put the rest of the function in an else
           | clause. You lose visual parallelism, and suddenly instead of
           | following a tree down to a series of leaves where every leaf
           | terminates, you have to have the full context to know whether
           | or not a line of code may not execute in some cases. For
           | example:                 function foo(x) {         if (x ==
           | null) {           return null;         }              x += 2;
           | return x;       }
           | 
           | I can't just look at "x += 2;" and know whether or not it's
           | conditionalized. I have to have the full context including
           | the early-return, and then reason about the control flow from
           | there. Whereas:                 function foo(x) {         if
           | (x == null) {           return null;         } else {
           | x += 2;           return x;         }       }
           | 
           | Here I can tell just from the else-block that this is one
           | possibility which will execute if the other one does not, and
           | vice-versa. Their indentation is the same, cementing their
           | relationship. If I want to know whether this block will
           | execute I need only look at the if()'s, not their contents.
        
             | tpoacher wrote:
             | I agree, but also I think this is misrepresenting the
             | 'early return' pattern. It's more likely to be succinct and
             | look like this instead:                   function check(x)
             | {             if (!test1 (x))   return false;
             | if (!test2 (x))   return false;             if (!test3 (x))
             | return false;             if (!test4 (x))   return false;
             | return true;         }
             | 
             | In _this_ scenario, it is true that this is much more
             | readable than:                   function check(x) {
             | if (!test1 (x)) {                return false;
             | } else if (!test2 (x)) {                return false;
             | } else if (!test3 (x)) {                return false;
             | } else if (!test4 (x)) {                return false;
             | } else {                return true;             }
             | }
        
               | tsimionescu wrote:
               | Never ever use if without curly braces. That's one of the
               | worst ideas of C's syntax.
               | 
               | Your first example would still look clean like this:
               | function check(x) {           if (!test1 (x)) { return
               | false; }           if (!test2 (x)) { return false; }
               | if (!test3 (x)) { return false; }           if (!test4
               | (x)) { return false; }           return true;       }
               | 
               | And it would be much safer from stupid copy/paste errors.
        
               | jhgb wrote:
               | Instead of                   function check(x) {
               | if (!test1 (x))   return false;             if (!test2
               | (x))   return false;             if (!test3 (x))   return
               | false;             if (!test4 (x))   return false;
               | return true;         }
               | 
               | why wouldn't you do something along the lines of
               | (every (lambda (f) (funcall f x)) (list #'test1 #'test2
               | #'test3 #'test4a))
               | 
               | or whatever is the equivalent in your preferred language?
        
               | wvenable wrote:
               | The tests are rarely that simple or regular.
        
               | jhgb wrote:
               | Isn't that exactly the reason to put them somewhere else?
        
               | wvenable wrote:
               | No, I just mean the actual tests might be something like:
               | if (user == null) return false;         if
               | (IsCompleted(user)) return true;         if (action ==
               | null) return false;         if (value < 0 || value > 100)
               | return false;
               | 
               | You're not usually just passing a single value to a bunch
               | of "test" functions.
        
               | brundolf wrote:
               | Worth noting that elses don't require curly braces, which
               | are where most of the visual noise is coming from in your
               | example:                 function check(x) {         if
               | (!test1 (x))      return false;          else if (!test2
               | (x)) return false;         else if (!test3 (x)) return
               | false;         else if (!test4 (x)) return false;
               | else                 return true;       }
               | 
               | I think this is more clear than either of the above
        
               | tpoacher wrote:
               | > Worth noting that elses don't require curly braces
               | 
               | True. I still think the first is clearer though. Either
               | all tests pass, or they don't.
        
               | tpoacher wrote:
               | (also I'd be very skeptical of such an if..else ladder.
               | Prime target for bugs.)
        
             | cryptonector wrote:
             | On the contrary, if early returns allow you to outdent a
             | larger portion of code then it's quite a readability win.
             | As long as all the early return cases are bite-sized and
             | the last case long-ish, it's a readbility win.
        
               | jbverschoor wrote:
               | And certain languages allow:
               | 
               | return nil if n.nil?
        
             | wvenable wrote:
             | For a single condition it doesn't matter much either way
             | for readability. But once you have a number of conditions
             | the if/else style becomes unwieldy.
             | 
             | I put all my early returns at the top as these are the
             | preconditions for the rest of the code; I don't think
             | that's hard to understand.
        
             | kbenson wrote:
             | I think your version isn't really as different as it seems
             | to some. To me, the essential bit of early return is that
             | you handle cases that eliminate the work first. That is,
             | it's less about singular if statements separating the rest
             | of the body of the function than it is clearly short
             | circuiting the code as early as is clearly possible.
             | 
             | That is, I don't think it matters too much whether you do:
             | function div(x,y) {           if (y == null) {
             | return null;           }           if (y == 0) {
             | return null;           }                    return x/y;
             | }
             | 
             | or                   function div(x,y) {           if (y ==
             | null) {             return null;           } else if (y ==
             | 0) {             return null;           } else {
             | return x/y;           }         }
             | 
             | as they both accomplish the same major benefit of the
             | pattern.
             | 
             | The real thing it's helping to avoid is accidentally
             | creating something like this:                   function
             | div(x,y) {           if (y != null) {             if (y !=
             | 0) {               return x/y;             } else {
             | return null;             }           } else {
             | return null;           }         }
             | 
             | which can quickly get very hard to reason about without in
             | more complex cases.
        
               | brundolf wrote:
               | You're right that both technically qualify as early-
               | return, which is why I qualified my original statement
               | with "if you don't put the rest of the function in an
               | else clause".
               | 
               | In practice, in C-like languages, I tend to see the
               | "else-less" version which is why I brought it up
        
             | t-writescode wrote:
             | I think the bigger problem you may be experiencing, if I
             | had to guess, code structure.
             | 
             | If people tend to write:                  function foo(x) {
             | if (x == null) {            return null;          }
             | // do some stuff to the code                    for(a
             | reason) {             // do some more stuff
             | if(some detailed reason) {               return null;
             | }               // more things          return 7;        }
             | 
             | Then the shape of the code and the "pattern" of early
             | returns gets broken.
             | 
             | If you read code where all of the short-circuit, early-
             | return logic is at the start of the function, and then all
             | of the work is done, do you still have this opinion?
             | 
             | E.g.                  function foo(x) {           if(x ==
             | 1) { return null; }           if(x == 2) { return null; }
             | if(x > 7) { return null; }                // do things with
             | x           return 7;        }     ?
        
               | brundolf wrote:
               | The problem is definitely much worse when returns are
               | scattered throughout the function instead of grouped at
               | the top, but I still prefer them to be "elsed" together
               | (or in a switch/cond/match statement or whatever). Worth
               | noting that in Lisp - one of the classical languages
               | where early-returning is popular - there isn't a problem
               | because it is done as I describe:                 (cond
               | (early-return-condition early-value)         (second-
               | early-condition early-value2)         ...         (t
               | final-else-value))
               | 
               | The "default case" is on the same footing as the other
               | cases. Only in procedural languages does the control-flow
               | get confusing if you aren't careful.
        
               | t-writescode wrote:
               | If you want elses everywhere, how do you protect yourself
               | from nesting hell? I find nesting hell to be far, far,
               | faaaar worse for readability than any of the
               | alternatives, and will move to the early return model to
               | escape all that nesting.
        
               | brundolf wrote:
               | I dunno, I just rarely ever find myself nesting more than
               | two or three levels of conditionals even in extreme
               | cases. If my logic really is that complicated, it's
               | probably a sign it needs to be broken up anyway. And if I
               | did need that much branching, I would want to be _more_
               | explicit about it, not less.
               | 
               | Also - at the very least - you can mimick the "flat"
               | early-return style and just stick some else's in there
               | and call it a day. That's my main point; less so the
               | actual deeper nesting
        
             | ufo wrote:
             | I have the same opinion. I like the early-return pattern
             | when it's doing input validation or raising errors but for
             | most other things I prefer how the if-else highlights that
             | there are two possible paths that the code can take. Just
             | because the "then" branch has a return statement doesn't
             | mean that we should get rid of the "else".
        
           | jbverschoor wrote:
           | Sadly, a lot of junior and medior programmers see that as a
           | wrong pattern, and opt for a return variable.. Drives me
           | nuts.
        
             | vsareto wrote:
             | They should really use both
        
             | speedyapoc wrote:
             | I recall this pattern being taught in my first year or two
             | of my computer science degree. Emphasizing only one return
             | statement per function, utilizing a shared return variable.
             | 
             | I much preferred (and still greatly prefer in industry) the
             | pattern of exiting the function as soon as possible.
        
               | jbverschoor wrote:
               | Same here.
               | 
               | Returning and throwing quickly and strict.
        
               | cryptonector wrote:
               | It's like "goto considered harmless" -- structured code
               | to max, to the point of unreadability. Dogmatism has
               | drawbacks.
        
             | joshAg wrote:
             | In C when you have nothing that needs cleanup, it's not the
             | most horrible thing ever:                 int foo(thing_t
             | \*work) {         int ret = E_SUCCESS;
             | if(work->thing == BAD_THING) {           ret = E_BAD;
             | goto exit;         }                do_some(work);
             | if(work->thing2 == OTHER_BAD_THING) {
             | LOG("whoopsies");           ret = E_OTHER;           goto
             | exit;         }                ret = do_more(work);
             | if (!ret) {           LOG("lazy");           goto exit;
             | }                ret = last_bit_of(work);
             | exit:         return ret;       }
        
         | eh9 wrote:
         | I would usually agree, but the suggested code would allow for
         | multiple errors to be shown before they're thrown.
        
         | high_byte wrote:
         | switch true is a cool new trick to me, but early return is the
         | way to go.
        
         | 1_player wrote:
         | Early return is my silent gauge to tell if a piece of code has
         | been written by a junior or a senior software engineer.
         | 
         | I still see far to many snippets with multiple levels of
         | indentation, where each if branch is for the happy path, and if
         | they were converted to early returns you could flatten the
         | whole thing to 1 indentation level, at the expense of requiring
         | negated boolean expressions which aren't as readable.
        
         | qudat wrote:
         | Agreed! I have three rules for writing functions:
         | 
         | - Return early
         | 
         | - Return often
         | 
         | - Reduce levels of nesting
         | 
         | https://erock.io/2019/07/11/three-rules-for-refactoring-func...
        
         | kvczor wrote:
         | Linting got messed up in the comment above, but in the actual
         | code this is nicely readable.
        
           | [deleted]
        
         | tengbretson wrote:
         | I've grown to dislike the early return pattern. It's pitched as
         | a way to reduce visual complexity by reducing levels of
         | indentation, but I don't actually find that to be a benefit in
         | most cases. Reducing indentation is just a trick to shove more
         | cyclomatic complexity into a function without it triggering
         | your sensibilities to implement proper abstractions.
        
         | mwkaufma wrote:
         | 1000X this.
        
         | elyseum wrote:
         | No need to 'return' when you throw an error, but your approach
         | is valid IMO: if structure / readability is that important,
         | refactor the switch to it's own method with only if checks in
         | it. Hard to make that simpler and more readable.
        
           | notyourday wrote:
           | If you don't have a 'return' there's nothing to say that some
           | time later some junior developer would not modify throw to be
           | something else or forget that throw does not return.
        
             | BiteCode_dev wrote:
             | That's what tests are for.
        
             | dfee wrote:
             | This will prevent the JR dev from making that mistake:
             | https://eslint.org/docs/rules/no-fallthrough
        
         | 0xcoffee wrote:
         | I've also seen code written as:                 _ =
         | !isDefined(user) && throw new Error("User must be defined.");
         | _ = !isString(user.firstName) && throw new Error("User's first
         | name must be a string");
         | 
         | But I while it is concise, I can also understand why people
         | prefer regular if statements.
         | 
         | Doesn't seem to be valid JS, can't remember where I got it from
        
           | IggleSniggle wrote:
           | My preference for this is to use an assertion function, eg in
           | nodejs:
           | 
           | assert(isDefined(user),"User must be defined") // throws if
           | Falsy
        
           | CloselyChunky wrote:
           | In my opinion this pattern is better if you write it like
           | this:                 _ = isDefined(user) || throw new
           | Error("user must be defined")
           | 
           | This reads way more natural for me. "A user is defined OR
           | throw an error"...
           | 
           | I've also seen this in Perl (`do_something() || die()`) and
           | shell scripts (`grep -q || die "not found"`).
        
             | pxx wrote:
             | In perl you would want to use `or` instead of `||` to take
             | advantage of the low precedence, so you can type things
             | like `dostuff $foo or die` which is logically 'do stuff,
             | and if it fails, die' as opposed to 'die if $foo is false',
             | which you'd get with ||
        
           | lhorie wrote:
           | It's a stage 2 proposal, which technically can be used today
           | via babel[0] (though personally, I don't recommend using
           | stuff below stage 4)
           | 
           | [0] https://babeljs.io/docs/en/babel-plugin-proposal-throw-
           | expre...
        
           | d1sxeyes wrote:
           | This pattern is quite common in React to do conditional
           | rendering:                 {isLoading && <Loading />}
        
         | qalmakka wrote:
         | So that has got a name? I always instinctively wrote code like
         | that, because it's much more easier to understand.
        
       | aastronaut wrote:
       | This style gets the label 'poor-mans-pattern-matching' from me.
       | If pattern matching would not be in my daily vocabulary, as it's
       | also not available in JS, I'd consider it a misuse of switch/case
       | and this post also makes an odd example for its usefulness.
       | 
       | The example I would pick is the following: Consider you need to
       | switch depending on a version (of a specification in my case),
       | but this version isn't represented as an enum in the codebase,
       | but as a number instead. So our team had something like this in
       | the codebase (early return):                 function
       | foobar(version: number): string {         if (version === 3.1 ||
       | version === 3) {           return 'result_3';         }
       | if (version < 2 && version >= 1) {           return 'result_1';
       | }                if (version >= 2) {           return 'result_2';
       | }                throw new Error(`Cannot interpret version
       | '${version}'`);       }
       | 
       | I read it as "people don't care about branching order that much,
       | so how can I make my wish for better readability more clear?"....
       | my end goal then was to bring it into this state (a distinct enum
       | as discrete value of the version):                 enum Version {
       | _1_1 = 1.1,         _2   = 2,         _3   = 3,         _3_1 =
       | 3.1,       };            function foobar(version: Version):
       | string {         switch (version) {           case Version._3_1:
       | case Version._3:             return 'result_3';           case
       | Version._2:             return 'result_2';           case
       | Version._1_1:             return 'result_1';           default:
       | (function (val: never): never {               throw new
       | Error(`Exhaustiveness reached: ${val}`);             })(version);
       | }       }
       | 
       | ...and my interim solution that made it into the PR in time
       | turned out to be something like this (switch true):
       | function foobar(version: number): string {         switch (true)
       | {           case version >= 3:             return 'result_3';
       | case version >= 2:             return 'result_2';           case
       | version >= 1:             return 'result_1';           default:
       | throw new Error(`Cannot interpret version '${version}'`);
       | }       }
       | 
       | My PR was flagged by the team for misuse of the switch statement,
       | we had some discussion and I changed it back to the simple
       | if/else branching from above.
        
         | alerighi wrote:
         | switch (Math.floor(version)) {         case 1:
         | return 'result_1';         case 2:             return
         | 'result_2';         case 3:             return 'result_3';
         | default:             throw new Error('...');         }
         | 
         | Isn't that more clear?
        
       | mwkaufma wrote:
       | Even accepting the dubious premise that "pretty text =
       | maintainable code", he's juked his exampled by (i) littering the
       | simple early-outs with unnecessary "else"s and (ii) stripping the
       | "break"s from his switch.
        
       | brundolf wrote:
       | It's interesting, but I can't decide if it's an anti-pattern or
       | not. You're abusing a construct to achieve a slight improvement
       | in brevity/readability, with the downside of JS's lack of block-
       | scoping for case statements which means variables in one case can
       | conflict with variables in other cases
       | 
       | All in all: I probably won't be using it
        
       | jypepin wrote:
       | I remember when I first discovered the concept of pattern
       | matching, I tried to find a way to "hack" the switch statement in
       | js to make it work like pattern matching.
       | 
       | The switch(true) format was the closest I got to it, which I
       | personally don't like compared to a clean if/else or early
       | return.
       | 
       | There's probably some performance differences between if/else and
       | switch (I haven't checked) but it probably end up being what's
       | your preference / what standard code style you want to enforce on
       | your team.
        
       | scotttrinh wrote:
       | Hopefully we will get real pattern-matching at some point
       | (https://github.com/tc39/proposal-pattern-matching), but I kinda
       | sorta like this!
        
       | [deleted]
        
       | [deleted]
        
       | vikingcaffiene wrote:
       | I'd flag this if it came up in PR. It's clever and novel which is
       | precisely why it's a bad idea. Always bet on boring.
        
         | xwolfi wrote:
         | Exactly, because it won't be the first innovation nor the last
         | and if you let them all through, your code becomes a bazaar of
         | curiosities rather than a business solution...
        
         | hatch_q wrote:
         | Same. I also flag seemingly good things like !!x. If you want
         | to convert something to boolean be explicit and do Boolean(x).
         | In most cases it turns out there was a bug in input parameter
         | in the first place and developer was just lazy to fix it.
        
       | encoderer wrote:
       | For many years I stopped using this after getting flack in pull
       | requests.
       | 
       | I've recently added it back into my toolkit and am reminded how
       | much I love it. Don't over use it but there are some really
       | gnarly if/else blocks that can be expressed beautifully with a
       | switch and fall-thru.
        
         | jbverschoor wrote:
         | If (x) throw y;
         | 
         | If (s) throw t;
         | 
         | Throw default;
        
       | jimjimjimjim wrote:
       | I think the beauty of the switch statement is that when you see
       | one you know you're just concentrating on the value of one
       | variable. I think the if else if is actually cleaner for the user
       | example in the post.
        
       | borishn wrote:
       | The article is misleading, in implying that `switch(true)` is a
       | special case: "The fundamental principle of the switch true
       | pattern is that you can match against expressions as well as
       | values."
       | 
       | It should be states as "The fundamental principle of the switch
       | pattern in JavaScript is that you can match against expressions
       | as well as values."
       | 
       | From https://developer.mozilla.org/en-
       | US/docs/Web/JavaScript/Refe...:
       | 
       | A switch statement first evaluates its expression. It then looks
       | for the first case clause whose expression evaluates to the same
       | value as the result of the input expression
        
       | 1_player wrote:
       | That's good and dandy until one changes one case block to normal
       | statements instead of a terminating one, forgets to add a
       | "break;" and someone has a nightmare debugging session trying to
       | figure what is going on.
       | 
       | Go did good by making case blocks break automatically and
       | requiring the "fallthrough" keyword in one of those very rare
       | cases you need it do.
        
       | jacknews wrote:
       | Is this a real thing? It looks incredibly hacky to me. What
       | happens when multiple cases are true, are they all handled? In
       | what order? What happens if one of them returns? Etc.
        
         | Kinrany wrote:
         | The same way switch works in every language and regardless of
         | the clever pattern: find the first expression that equals and
         | jump to that label.
        
           | int_19h wrote:
           | A better question is: which "case" expressions are evaluated
           | - all of them, or only the ones enumerated before the match
           | was found?
           | 
           | Unless you use this pattern regularly - and most coders
           | don't, even those who mostly write JS - you'll probably have
           | to look the answer up. That alone is a good reason to stick
           | to if/else; there's no such ambiguity there.
        
             | lifthrasiir wrote:
             | > A better question is: which "case" expressions are
             | evaluated - all of them, or only the ones enumerated before
             | the match was found?
             | 
             | Good point. By the way the answer is the latter, so the
             | following prints 1 and 2.                   switch (true) {
             | case (console.log(1), false):             case
             | (console.log(2), true):             case (console.log(3),
             | false):                 break;         }
        
           | tsimionescu wrote:
           | Many languages don't allow duplicate values for case in
           | switch statements, so that question never comes up - this is
           | true for C, C++, C#, Java. Go, Ruby do behave like JS here.
        
           | mlyle wrote:
           | But in many languages, the cases need to be compile-time
           | constant rvalues.
        
       | erikerikson wrote:
       | Validation code as shown tends to be repetitive and imperfectly
       | implemented. I have found that transitioning to using AJV and
       | JSON Schema is far more sustainable, especially on an API
       | surface. One describes the data and depends on consistent and
       | vetted logic for validating the described types rather than
       | repetitively describing how to validate them.
       | 
       | Validations that happened at an application level must still be
       | written but those tend to be specific to the application logic or
       | system state. An example of logic related validation is
       | contingently valid argument values where the compatibility of one
       | value being used with another must be tested. An example of state
       | related validation is a constraint that a given value must exist
       | in a dynamic table.
        
       | crishoj wrote:
       | While readable and aesthetically pleasing, I find myself
       | wondering about the performance implications of switch(true)
       | versus a multi-branched if-else. Does V8 (and PHP) treat each
       | construct differently when it comes to optimizations? We're not
       | in C-land here, so jump tables are presumably not in play.
        
       | [deleted]
        
       | jbverschoor wrote:
       | The example shouldn't be compared to esleif, that's not how
       | switches work.
       | 
       | Also, because it throws you can just use if, without a block. Or
       | use if at the end of the line if your language supports that.
       | 
       | Way cleaner (less indentation), and less error prone. Switch
       | statements only exist because of the underlying assembly/opcode.
       | 
       | It just as bad as goto, because it IS goto. The cases are goto-
       | labels. It behaves like goto, and will simply generate the same
       | JE/JNE jumps
        
       ___________________________________________________________________
       (page generated 2021-04-12 23:01 UTC)