Which would look like this:
const user = {
firstName: "Seán",
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;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.
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.
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.
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.Not sure it makes much sense to indent everything within "if" and if you forget to "else", you've just potentially hidden a bug.
- Return early
- Return often
- Reduce levels of nesting
https://erock.io/2019/07/11/three-rules-for-refactoring-func...
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.
_ = !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
_ = 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"`).
[0] https://babeljs.io/docs/en/babel-plugin-proposal-throw-expre...
assert(isDefined(user),”User must be defined”) // throws if Falsy
{isLoading && <Loading />}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.
> 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.
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 really want this. Along with switch being as expression. Seems like something that JavaScript is obviously missing.
I agree that overly clever code is always to be avoided. But is this really overly clever? I wouldn't expect an engineer early in their career necessarily to understand on sight what it does. But I might worry a little for one who couldn't figure it out through experiment.
[edit: fixed typo]
You're going to hate your former cleverness then.
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.
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;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.
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.
One reason I like RustLang is it treats this as an ergonomic issue by appending `?` for early returns, without block-nesting. So nice.
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.
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). 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.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. 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?A good (IMHO) early-return pattern would look like this:
if (version >= 3.1) {
return "result_3_1";
}
if (version >= 3) {
return "result_3";
}
// ...
...but in the wild I often see it developed into something like this: if (version >= 3) {
if (version === 3.1) {
return "result_3_1";
}
return "result_3";
}
// ...
...probably because people like to see major version numbers packaged together in blocks.With the switch-true on the other hand I could make good use of this 'stepping through ranges'-problem and could even make use of the fallthrough, in case it should fall back to a different value:
switch (true) {
case version >= 3.1:
return "result_3_1"; // just comment this out if you need the result of version 3 instead
case version >= 3:
return "result_3";
// ...
} function foobar(version: number): string {
if (version >= 3) { return 'result_3'; }
else if (version >= 2) { return 'result_2'; }
else if (version >= 1) { return 'result_1'; }
else {
throw new Error(`Cannot interpret version '${version}'`);
}
}
?I don't see what advantage the switch provides here.
...
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
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 "¯\\_(ツ)_/¯"
}
}
function getArrow(direction) {
const arrows = {
left: "<--",
right: "-->",
default: "¯\\_(ツ)_/¯",
}
let arrow = arrows[direction]
if (arrow) {
return arrow
} else {
return arrow.default
}
} function getArrow(direction) {
const missing = '¯\\_(ツ)_/¯'
const arrows = {
left: '<--',
right: '-->',
}
return arrows[direction] || missing
}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.
If (s) throw t;
Throw default;
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.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.
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.
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
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.
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
All in all: I probably won't be using it
This is wrong, Since there is no return or break default will also be executed.
— Programming in JavaScript is a bad pattern itself.