const user = {
firstName: "Seán",
lastName: "Barry",
email: "[email protected]",
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;
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.
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.
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.
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.
_ = !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
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.
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.
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.
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.
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.
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.
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.
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...
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;
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.
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.
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.
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.
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.
Good lord this is awful. If somebody's paying you to solve problems with code, please just write clear code, rather than showing off. Somebody's going to have to make sense of it a year from now when requirements change, and you will be in a sense talking to that future programmer (maybe it's you) via code. You should be trying to tell them about the problem, rather than about yourself.
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.
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).
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.
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.
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."
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
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.
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.
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.
[+] [-] kvczor|5 years ago|reply
Which would look like this:
[+] [-] thomas_moon|5 years ago|reply
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.
[+] [-] qudat|5 years ago|reply
- Return early
- Return often
- Reduce levels of nesting
https://erock.io/2019/07/11/three-rules-for-refactoring-func...
[+] [-] 1_player|5 years ago|reply
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.
[+] [-] elyseum|5 years ago|reply
[+] [-] qalmakka|5 years ago|reply
[+] [-] tengbretson|5 years ago|reply
[+] [-] 0xcoffee|5 years ago|reply
Doesn't seem to be valid JS, can't remember where I got it from
[+] [-] high_byte|5 years ago|reply
[+] [-] kvczor|5 years ago|reply
[+] [-] btown|5 years ago|reply
[+] [-] darepublic|5 years ago|reply
[+] [-] mwkaufma|5 years ago|reply
[+] [-] eh9|5 years ago|reply
[+] [-] cced|5 years ago|reply
[+] [-] elyseum|5 years ago|reply
[+] [-] csmattryder|5 years ago|reply
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.
[+] [-] TheHalfDeafChef|5 years ago|reply
[edit: fixed typo]
[+] [-] TeMPOraL|5 years ago|reply
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.
[+] [-] vikingcaffiene|5 years ago|reply
[+] [-] hatch_q|5 years ago|reply
[+] [-] xwolfi|5 years ago|reply
[+] [-] harg|5 years ago|reply
E.g.
[+] [-] CloselyChunky|5 years ago|reply
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:
(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.
[+] [-] _greim_|5 years ago|reply
One reason I like RustLang is it treats this as an ergonomic issue by appending `?` for early returns, without block-nesting. So nice.
[+] [-] jacknews|5 years ago|reply
[+] [-] Kinrany|5 years ago|reply
[+] [-] scotttrinh|5 years ago|reply
[+] [-] graderjs|5 years ago|reply
[1]: https://github.com/kdeldycke/awesome-falsehood#emails
[2]: https://github.com/kdeldycke/awesome-falsehood#phone-numbers
[+] [-] erikerikson|5 years ago|reply
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.
[+] [-] aastronaut|5 years ago|reply
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):
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): ...and my interim solution that made it into the PR in time turned out to be something like this (switch true): 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|5 years ago|reply
[+] [-] bananaface|5 years ago|reply
I don't see what advantage the switch provides here.
[+] [-] Sophistifunk|5 years ago|reply
[+] [-] molszanski|5 years ago|reply
It has an additional benefit of extracting code into data structures or making them parametric
[+] [-] hunterloftis|5 years ago|reply
[+] [-] standardUser|5 years ago|reply
[+] [-] alerighi|5 years ago|reply
[+] [-] hunterloftis|5 years ago|reply
[+] [-] jbverschoor|5 years ago|reply
[+] [-] jimjimjimjim|5 years ago|reply
[+] [-] 1_player|5 years ago|reply
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.
[+] [-] unknown|5 years ago|reply
[deleted]
[+] [-] unknown|5 years ago|reply
[deleted]
[+] [-] borishn|5 years ago|reply
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
[+] [-] encoderer|5 years ago|reply
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|5 years ago|reply
If (s) throw t;
Throw default;
[+] [-] mwkaufma|5 years ago|reply
[+] [-] seanbarry|5 years ago|reply
[deleted]
[+] [-] jypepin|5 years ago|reply
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.