Discussion was locked because I moved away from DEV to blog on my personal website. If you want to chat about the content of any article, hit me up in Mastodon
It was the first day in my last year of tech high school. The new programming teacher arrived and stood silent for a second, and then he started the lesson:
This year we will create a state machine with persistence using C++. This state machine will be a light-bulb that can be turned on, or off.
We all just look at each other thinking "ok, that will be easy"... and then he dropped the bomb:
There's a catch: You'll not be allowed to use
if
orfor
for it.
Now the class was clearly confused. Flow control is one of the first things we all learn as programmers. The main objective of that teacher was to teach us that we need to stop thinking conditions as if
, repetitions as for
, and so on, and instead be more abstract with the logic on our code. In JavaScript we have:
-
if/else
. -
for/of/in
. -
while
. -
do/while
. -
switch/case
. -
try/catch
.
We will go through that list and learn about some of the alternatives we have, that from my point of view are generally safer and cleaner. Let's begin!
Conditions (if/switch)
Let's take this simple example as a starting point:
const welcomeMessage = ({ admin }) => {
let message;
if (admin) {
message = "Welcome, administrator!";
}
return message;
};
So we have a function welcomeMessage
which takes a user object and returns a message which depends on the user role. Now, because this if is quite simple, we might spot already that this has an issue, but JavaScript itself doesn't give us any kind of error. We don't have a default value for that message, so we need to do something like this:
const welcomeMessage = ({ admin }) => {
let message = "Welcome, user";
if (admin) {
message = "Welcome, administrator!";
}
return message;
};
// Or
const welcomeMessage = ({ admin }) => {
let message;
if (admin) {
message = "Welcome, administrator!";
} else {
message = "Welcome, user";
}
return message;
};
As I said in the introduction, we don't need if
for this, we can use a ternary instead. A ternary has this shape:
boolean ? valueForTrue : valueForFalse
So we can change welcomeMessage
to be like this:
const welcomeMessage = ({ admin }) =>
admin ? "Welcome, administrator!" : "Welcome, user";
// Or
const welcomeMessage = ({ admin }) =>
`Welcome, ${admin ? "administrator" : "user"}!`;
Ternaries have 3 advantages over ifs:
- They force us to cover all the logic branches (we are forced to have "else in all our ifs").
- They reduce the amount of code drastically (we just use a
?
and a:
). - They force us to use conditional values instead of conditional blocks, which results in us moving logic from if blocks to their own functions.
The main argument against ternaries is that they become hard to read if we have several levels of nested if
s (if
s inside an if
s), and that's true, but I see that as yet another advantage. If you need to nest logic, that means that you need to move that logic away. So, let's have yet another example for this:
const welcomeMessage = ({ canMod, role }) =>
`Welcome, ${
canMod ? (role === ADMIN ? "administrator" : "moderator") : "user"
}!`;
That became hard to read quite easily, but that means that we need to move some logic away from welcomeMessage
, so we need to do something like this:
const roleText = role => (role === ADMIN ? "administrator" : "moderator");
const welcomeMessage = ({ canMod, role }) =>
`Welcome, ${canMod ? roleText(role) : "user"}!`;
We covered if
already, but what about switch
? We can use a combination of plain objects and the ??
operator, so we go from this:
const welcomeMessage = ({ role }) => {
switch (role) {
case ADMIN:
return "Welcome, administrator!";
case MOD:
return "Welcome, moderator!";
default:
return "Welcome, user!";
}
};
To this:
const roleToText = role =>
({
[ADMIN]: "administrator",
[MOD]: "moderator"
}[role] ?? "user");
const welcomeMessage = ({ role }) => `Welcome, ${roleToText(role)}!`;
For those not familiar with the ??
operator, it works like this:
possiblyNullishValue ?? defaultValue
possiblyNullishValue
can be either a value or "nullish" (null
or undefined
). If it is nullish, then we use defaultValue
, if it isn't nullish then we use the value itself. Previous to this, we used to use ||
, but that goes to the default for all falsy values (0
, 0n
, null
, undefined
, false
, NaN
and ""
), and we don't want that.
Error handling (try/catch).
When we want to run something that might throw an error, we wrap it with a try/catch
, as follows:
const safeJSONParse = value => {
let parsed;
try {
parsed = JSON.parse(value);
} catch {
// Leave `parsed` `undefined` if parsing fails
}
return parsed;
};
const works = safeJSONParse("{}"); // {}
const fails = safeJSONParse(".."); // undefined
But we can get rid of that as well, using Promises. When you throw inside a promise, it goes to the catch
handler automatically, so we can replace the code above with:
const safeJSONParse = value =>
new Promise(resolve => resolve(JSON.parse(value)))
// If it fails, just return undefined
.catch(() => undefined);
safeJSONParse("{}").then(works => ({
/* {} */
}));
safeJSONParse("..").then(fails => ({
/* undefined */
}));
Or you can just use async/await
and...
const works = await safeJSONParse("{}"); // {}
const fails = await safeJSONParse(".."); // undefined
Loops (for/while)
The for
and while
statements are used to loop over a "list" of things, but nowadays we have way better ways of doing that with the methods that come with some of those lists (arrays) or other functions that help us keep the same type of looping for objects as well. So let's start with the easiest, which is arrays:
const users = [
{ name: "Luke", age: 32 },
{ name: "Gandalf", age: 24_000 }
];
// Just logging
for (const { name, age } of users) {
console.log(`The age of ${name} is ${age}`);
}
// Calculating average
let ageTotal = 0;
for (const { age } of users) {
ageTotal += age;
}
console.log(`The average age is ${ageTotal / users.length}`);
// Generating new array from previous
const usersNextYear = [];
for (const { name, age } of users) {
usersNextYear.push({ name, age: age + 1 });
}
Instead of using for
for this, you can just use the Array.prototype.forEach
for the logs, Array.prototype.reduce
for the average and Array.prototype.map
for creating a new array from the previous one:
// Just logging
users.forEach(({ name, age }) => console.log(`The age of ${name} is ${age}`));
// Calculating average
console.log(
`The average age is ${users.reduce(
(total, { age }, index, items) =>
(total + age) / (index === items.length - 1 ? items.length : 1),
0
)}`
);
// Generating new array from previous
const usersNextYear = users.map(({ name, age }) => ({ name, age: age + 1 }));
There is an array method for pretty much everything you want to do with an array. Now, the "problems" start when we want to loop over objects:
const ages = {
Luke: 32,
Gandalf: 24_000
};
// Just logging
for (const name in ages) {
console.log(`The age of ${name} is ${ages[name]}`);
}
// Calculating average
let ageTotal = 0;
let ageCount = 0;
for (const name in ages) {
ageTotal += ages[name];
ageCount += 1;
}
console.log(`The average age is ${ageTotal / ageCount}`);
// Generating new object from previous
const agesNextYear = {};
for (const name in ages) {
agesNextYear[name] = ages[name] + 1;
}
I put the word "problem" between quotes because it was a problem before, but now we have great functions in Object
: Object.entries
and Object.fromEntries
. Object.entries
turns an object into an array of tuples, with the format [key, value]
, and Object.fromEntries
takes an array of tuples with that format, and returns a new object. So we can use all the same methods we would use with arrays, but with objects, and then get an object back:
// Just logging
Object.entries(ages).forEach(([name, age]) =>
console.log(`The age of ${name} is ${age}`)
);
// Calculating average
console.log(
`The average age is ${Object.entries(ages).reduce(
(total, [, age], index, entries) =>
(total + age) / (index === entries.length - 1 ? entries.length : 1),
0
)}`
);
// Generating new object from previous
const agesNextYear = Object.fromEntries(
Object.entries(ages).map(([name, age]) => [name, age + 1])
);
The most common argument about this approaches for loops is not against Array.prototype.map
or Array.prototype.forEach
(because we all agree those are better), but mainly against Array.prototype.reduce
. I made a post on the topic in the past, but the short version would be: Just use whatever makes the code more readable for you and your teammates. If the reduce approach ends up being too verbose, you can also just do a similar approach to the one with for
, but using Array.prototype.forEach
instead:
let ageTotal = 0;
users.forEach(({ age }) => (ageTotal += age));
console.log(`The average age is ${ageTotal / users.length}`);
Edit: Improving readability
I knew I was forgetting something when I published the article, but the idea with the approach using array methods is also to move logic to functions, so let's take the last example of looping over objects and make it cleaner:
// If we will do several operations over an object, ideally we save the entries
// in a constant first...
const agesEntries = Object.entries(ages);
// We extract logic away into functions...
const logNameAndAge = ([name, age]) =>
console.log(`The age of ${name} is ${age}`);
const valueAverage = (total, [, value], index, entries) =>
(total + value) / (index === entries.length - 1 ? entries.length : 1);
const valuePlus1 = ([key, value]) => [key, value + 1];
// Now this line is readable...
agesEntries.forEach(logNameAndAge);
// Calculating average
console.log(`The average age is ${agesEntries.reduce(valueAverage, 0)}`);
// Generating new object from previous
const agesNextYear = Object.fromEntries(agesEntries.map(valuePlus1));
And not only more readable, but also now we have generic functionality that we can reuse such as the valueAverage
or valuePlus1
.
The other thing I forgot that usually replaces for
and while
is recursion (function that calls itself), but I don't usually use recursion myself. So, let's only do the sum of an array of numbers:
const sum = array =>
array.length > 0 ? sum(array.slice(1)) + array[0] : 0;
sum
takes an array, and calls itself until no array is left, adding the values in it and finally returning the total.
Closing thoughts
I want to emphasize something that usually gets lost in this series of articles I'm doing: The keyword in the title is NEED. I'm not saying you shouldn't use if
/for
/while
and so on, I'm just saying that you might not need them, that you can code without them, and in some scenarios is even simpler (the majority of scenarios from my point of view). One of the names I considered for this series was "re-evaluating our defaults", because what I'm looking for is not to change 100% of your coding style, but actually to make you wonder:
Do I really NEED to do this, or is there a simpler way?
So, as usual, my final question for you is: Do you think you need if
, for
, while
, and so on? Don't you think there might be a better way of solving that same issue with a simpler approach?
Thanks for reading this and if you disagree with something said in this post, just leave a comment and we can discuss it further.
See you in the next post of this series!
Disclaimer
This series is called "You don't need ...", emphasis on need, meaning that you would be fine without the thing that the post covers. This series explores alternatives, it doesn't impose them, so consider that before glancing over the post and ranting on the comment section. Keep it respectful.
Top comments (71)
Interesting article, definitely it shows a different way to think, but, I think we need to have in mind that readability is not about how long is the code or how many sentences we have in a file, is about structure and experience. As an example, if you’re working with a junior dev maybe a ternary operator is not the best option to make the code readable for him, and in many cases the compiler/transpiler/interpreter transform the if and the ternary operator in an if.
A few things:
array.map(/* a bunch of code here inline */)
to something likearray.map(double)
which is way more readable. I update the article with a new section that I forgot from my notes here (new section "Edit: Improving readability").else
for an if, the minifier might turn it intocondition?value:undefined
, which isn't ideal. Ideally you should change thatundefined
, unless you intentionally wanted to return a "nullish" value.Thanks for reading!
Edit: Just to add to this, the post wasn't even pointed at juniors, it's set to
7
which is "almost expert", so yeah, this is more of a post for folks that are "stuck in their ways".Just a note: readability isn’t about understanding the concept or not. If a dev doesn’t know how to read a ternary at all, that’s a blocker. Readability is about how fast you understand the code by glancing at it. If a dev needs to pause to decipher a ternary ( maybe a multi level one), that’s a readability issue.
Code doesn’t need to be clever, it needs to be maintainable.
That's a pretty common complaint about ternaries, but I'll argue that this:
Is a readable (or maybe even more) than this:
I learnt ternaries when I was learning programming, and it was a pretty easy concept to grasp (for me and my classmates as well). Usually I see this "ternaries are hard to read for juniors", but the thing is that if becomes hard to read, that's a sign that you need to move some functionality away to its own function or reconsider part of your logic (as I mentioned in the article, I see that as a pro). If you find that you can't think conditions without thinking in the
if
keyword, that might be a problem. The idea is that you should be able to think logic branches as actual branches and not as "if/else" blocks. Don't get me wrong, I don't support stuff like nesting ternaries inside ternaries or anything like that that will become quite cryptic in no time, but as I mentioned in the article, ternaries have some advantages overif
s that from my point of view are pretty valuable.As you said, devs shouldn’t need to pause to decipher code in general, my point is that when using ternaries, if you need to stop to decipher, that only means that you need to move that code to a function with a clear name so we can abstract that "blockage" away.
Thanks for reading and commenting!
well, for a large part readability is in the eye of the beholder and such decisions should be taken based on team discussions and clear metrics.
But, three small details, just for the sake of the discussion:
-> as a nitpick, you don't need an else at all. You have return statements.
if
's can also be taken in separate functions to the same effect with the added bonus that you'd never need to use else in that situation, you'd just return.The top advantage of
if
is that in most languages you don't re-introduce other symbols and/or change their meaning.if (condition) {
is a pretty clear statement using the same block separators. That block may have as many or as few lines as needed, which isn't the case of a ternary that's meant to be used only in the simplest of cases. So then why have a double standard at all?if
statements are also portable between languages, while ternaries can be different (if they exist at all). Just look at PHP and how many different versions of ternaries it has. Fewer lines of code doesn't always mean more readable. Depending on context it can have the opposite effect.Granted, I'd use a ternary in one case (and one alone), to replace
to
though I'd still ask myself if it's worth creating a double standard and slightly increase cognitive load since I can also improve my code with
I would agree about the readability, but I think the biggest benefit of a ternary in cases like these is being able to use const directly
100% agree. For me, for example, the early return approach is less readable, I always try to keep my functions with a single point of output. When I see a block like:
I read "If
condition
then return"true value"
, return"false value"
" ... I know that the early return will break the function so the"false value"
woundl't run, but is just less readable from my point of view than just doing:Which I read as "
condition
? Then"true value"
. Else"false value"
". I actually would prefer anif
/else
over an early return.I mean, ternaries are pretty much everywhere, but even if you come from a language that doesn't have them, is a pretty easy concept to understand.
I mentioned this on the article, but I see that as yet another advantage of ternaries, mainly because if you need more logic in that condition, then you need to move that logic away to its own function. You can do this with ifs as well, but is not "enforced" by the language, you just have to agree with your whole team to try to keep conditions simple and move complex branches to their own functions.
I agree, that's why in the past I wrote an article talking about the importance of naming. I love functional programming, but for example hate how Haskell uses single letters for everything, making it short but extremely and unnecessarily hard to read. My idea is that if you move logic away to keep your ternaries short, those functions need to have significant names that make the content of that logic branch obvious without having to go into the function to see what it does.
Thanks for adding comments like this one! Actually showing more examples makes it way more useful for everyone!
Great lessons here. But one small obvious thing to spot out:
const ages = {
Luke: 32,
Gandalf: 24_000
};
I don't know but, i think
ages
sounds more like a name for an array as is a plural.Yup, ideally it shoul be named something like
nameAgeDictionary
ornameAgeMap
or something like that. I just usedages
because the main part of those examples is what follows ☺️amazing article, although id argue that if there are multiple nested ternaries snd you separate them in yheir own functions, it might require the dev to jump around the code, which might make it less readable
Maybe, yes. But my point is mainly that you should name those functions in a way that doesn't require the dev to jump to it, that the implementation is obvious without having to go take a look at it.
hmmm...and what if we actually have no fitting name for an intermediate effect? or the concern of the code becoming "too broken"?
It should always be possible to name a "branch" in logic, mainly because you're doing something in one branch, and something else on other, so those actions have names. But I understand your concern about the concern being "too broken", because it might feel like the logic is "all over the place". The thing is, that kinda forces us to something good that is "keeping it simple", if something is too complex, maybe we should consider making that logic a little bit simpler? Maybe this function is doing "way too much"? I tend to keep my functions below 10 lines of code (or even 5). This makes testing and maintenance way easier because every function does "one thing and does it well".
yeah, but what if the logic is actually complex with no way of simplifying it?
btw thanks for the informative post!
Our work as devs could be summarized in that: "yeah, but what if the logic is actually complex with no way of simplifying it?" 🤣 ... yup, in that case if you have something super complex and not easy to either split or simplify, then go ahead and use an
if
until you find a better way. If you keep thoseif
isolated to complex issues only, then your code will be far easier to maintain, and when you want to work on "debt", is easy to find, just look in your codebase for thoseif
that you weren't able to simplify before 😄now thats a good advice, thanks again!
You're right! I completely forgot to show how it looks like when you move stuff to functions with clear names, so you go from
array.map(/* a bunch of code here inline */)
to something likearray.map(double)
which is way more readable. I update the article with a new section that I forgot from my notes here (new section "Edit: Improving readability"). Thanks for reading!Is it actually more readable? I guess that's up to the reader. I personally don't think it's more readable, and really struggle with these multi operation one liners.
I think the author of the code may think it's more readable because they are on their second or third iteration of the code, refactoring and condensing. To them it's more readable because they've looked at it for so long it actually does become more readable! But for someone just seeing the code for the first time, sometimes it's not that clear.
I'd take your advice lightly. The fact that you felt compelled to write an article to show people how to remove if statements might be a signal that not everyone is as versed as you might be in this style, and it's better to code in a way that reaches the largest audience.
If you compare the Angular codebase vs Webpack codebase you will see two different styles. One definitely is more favorable to open source. I'll let you judge which one it is as you read the code :).
That's not what the article is about. Is just to show that
if
is not the only way of writing conditions, andfor
/while
is not the only way of writing iterations.I mentioned already several times in the article and in comments that if the code stops being readable, is a sign that you need to move some logic away to its own function with a clear and concise name. The idea with going with a less verbose version of the code isn't only to make it shorter, but similar to english when you use a word like "explicit" instead of writing "clearly stated", you create new "words" (functions) that represent longer "sentences" so your "text" is still clear yet less verbose.
So many of your alternatives are either painfully cryptic, almost as if it's got that TLDR nose-breather attitude behind it. Tale your CASE example. Which one is clearer more comprehensible code? Certainly doesn't help with JS object lookups being so slow it's also slower executing.
Your "for" vs "Array.forEach" example being a classic "how inefficient and how many unnecessary execution steps can we add" -- for ZERO improvement in code legibility. Just as the CONST in your loop helps card-stack its performance by adding extra checks to the process. Why would you add the overhead of a function call to each and every iteration of the loop?!? That's what Array.forEach does.
Because guess what? for..of is the NEWER of the two... and was added BECAUSE the overhead of callbacks for each and every iteration can be painfully wasteful.
The function call overhead being why your "lets make a function for every joe blasted line of code" allegedly "improved" version is bloated, inefficient, and HARDER to work with, not easier.
I promised myself I wouldn't answer unrespectful comments, but is kinda puzzling that you seriously find this:
Has zero gains in legibility over this:
Or this:
When the first one literally reads "array for each log", vs. the second one that reads "for i, while it's smaller than the length of array, add 1 to it and log array[i]", or the third one that reads "for item of array, log item".
Or you find the switch vs. object approach "cryptic", when I'm not even the only one doing this ... but ok.
No disrespect was meant to the individual, just to the work... which I found to be the opposite of the claims,
Though nice attempt at card-stacking the most out of date version of doing that.
Which has BETER clarity than forEach, without the overhead of the callback functions. That's why for..of was introduced AFTER Array.foreach. Don't add extra steps inside the loops that you don't have to.
I am punshing myself for admitting it, but @deathshadow60 is absolutly right...
for...of is way better (readwise and performancewise) than array.foreach or the for loop crap you seem to like...
No need to call "crap" something you don't like. And you aren't taking the full picture into consideration, is not only
forEach
, we also havemap
,filter
and so many other more idiomatic/readable ways of dealing with loops thanfor...of
:And we can go on. And the performance ... welp ... that depends on the engine I guess, but in Chrome using methods can be faster
I don't know you personally and take my words as no offense, but the better developer you get the less you show off in your code with little hacks and obscure language features (They're fine in coding competitions though).
I defenitely am a fan of using some of those in simple cases like:
over
but not in all situations.
Is definitively not for all situations. The post point is to avoid using only
if
when we talk about conditions, onlyfor
/while
when we talk about looping, and so on, and instead think logically in a pure way, instead of thinking in a "syntactic" way.Now about what you said:
I agree, but I don't see how promises, ternaries or array methods are "obscure" or "hacky" in any way. From my pont of view is way more hacky when folks do stuff like in this comment where they use
+
to do type coercion, or when they do short-circuits likecondition && trueResult
instead of just using a ternary, and so on. But using ternaries, promises and array methods is just using tools that the language actually provided.By definition hack means using something in a clever way that was not (at least at first intended) to use.
So using
Promise
which has a very definitive and clear usage out of its context as an exception handling mechanism seems hacky to me and may be to others as well.Next point is, programming (specially in productive environments) should be treated as verse as opposed to poem in a sense that you use words for their exact meanings rather than metaphors or implications or so. In that context when you see something like
if
you're 100% sure that you're dealing with a conditional, or be it atry/catch
for exception handling; But when you use promise for that matter the struggle begins.I beg to differ. A Promise is a proxy for a value not necessarily known when the promise is created. When you "try" something, is because that something might fail, so you don't know how it will go. That's why when you throw inside a Promise, you go to the
.catch
of that promise. If I had to write it like this......I would agree with you, but the implementation is way simpler because Promises deal with throws automatically by rejecting the promise. Using a promise for that is not being hacky from my point of view. Heck, we can even say there is not much difference between saying "I'll try to get that value" and "I promise I'll get that value". "Promise" sounds more committed, but functionally we deal with them the same way, that's why
async
/await
was so easily implemented withtry
/catch
, because it works pretty much the same way.I agree with you that "programming should be treated as verse as opposed to poem", but sticking to one way of doing stuff like for example only using
if
when we are thinking of conditions orfor
/while
for loops is very limiting to the way you'll express your logic in code.A few examples:
Shorter code doesn't always mean you're being "clever", sometimes it only means you're getting rid of boilerplate code to keep it concise but still readable.
I will not be a nitpicker anymore but just to clarify,
Promise
is not executed on the program's main thread and has its own task queue so it's not exactly the same astry/catch
and you're expanding its definition; Although that might be a good choice in a specific scenario.In your next point you're comparing two different paradigms of coding (procedural and somehow functional) which are not the exact same thing and has some different mechanisms for expressing different things. I'll stick to use the right one for the right context, mixing them won't help readability.
I use most of these constantly but often i don't too and the reason is the readability of the code deteriorates when it is too compact.
That actually depends on how you write it. In the examples I completely forgot to show how it looks like when you move stuff to functions with clear names, so you go from
array.map(/* a bunch of code here inline */)
to something likearray.map(double)
which is way more readable. I update the article with a new section that I forgot from my notes here (new section "Edit: Improving readability"). Thanks for reading!I agree, things can go south pretty quickly. I've seen my fair share of nested ternary statements at work
Nested ternaries are as bad practice as nested ifs are. I cover this in the 5th snippet in the article (the last ternary snippet, just before the switch alternative snippets), but basically, if you need to nest logic, that usually means that you need to move logic away to it's own function.
"You don't need if logic, just use the syntactically different if logic instead!"
"You don't need to iterate! just use the language's iterator functions instead!"
I guess I got clickbaited pretty hard for this article. Kudos.
Can't believe kids these days
You missed the point. The idea is that we have more ways of thinking in conditions than
if
, or in iterations thanfor
orwhile
. Those aren't the only tools we have, and we should avoid thinking in conditions asif
s or iterations asfor
s and try to think it in a more "abstract" way.I would close with something like "boomers this days", but I don't assume age based on coding style. I saw people 30+ years old and coding with the latest trends, and I saw 20- years old folks coding with jQuery and PHP. Age means nothing, just how open you are to learn new stuff.
Cheers!
Why do administrators get punctuation, but users don:t?
Are we not good enough, admins?There's another interesting pattern you can use, by combining an array with the knowledge that false will be coerced to the number zero and true to one, e.g.
I usually avoid "hacky" code such as short-circuiting and coercion. I'll literally prefer this because of readability:
Because we are being more "clear" about our intention. Not to mention that if we add more roles, this will become worse. Nevertheless this is interesting, thanks for sharing!
I wouldn't consider manual coercion "hacky", unlike automatic coercion. It's a normal language feature of weak typed languages. The problem are a lot of programmers who believe the language should behave differently than it is specified.
You're doing "automatic" coercion when you write
+boolean
, because the+
operator is automatically turning thatboolean
intonumber
. Ideally you should use something likeparseInt
,parseFloat
orNumber
if you want to coerce manually in a clear way.I am in the same boat: replace as many if statements as possible and I must say the code looks a LOT more cleaner.
But, I found more ways to remove some if statements thanks to this article.
Thank you!
Happy to be useful! Thanks for reading! ☺️
There was a time when the size of your JS files could make or break the experience of a web site, these days it's not typically a problem though. So I'd say reducing the "size" of the code isn't actually worthwhile if it doesn't improve readability as well. Any code base that has more than one contributor should be conscientious about the readability over performance so long as the performance is "good enough".
The point of the post is just to be less verbose and keep our logic concise. The point of the post is not about optimizing the size of the bundle, even if I disagree with you about that not being a problem nowadays. There are lots of folks saying "internet is good enough now" and forgetting not only folks around the world, but also that even if you have the best mobile data, there are places where the internet is flaky and you'll notice the extra kb in a Webapp (subways, rural zones, some condos, and so on).
That's what minifiers are for, a far better solution than deliberately harming readability in favor of conciseness in the code base.
Still missing the point of the post. The idea is not to "minify" the code, or write it in a short version just to write it in a short version ... the idea is to write it in a short version if that makes the code easier to read, for example you wouldn't do this:
When you could just:
Is shorter, but clearer. At the same time you shouldn't do this:
When you can do this:
Basically the idea is not "write it short", but "write it with less boilerplate if that helps making the code clearer or cleaner".
Whoa.
Some interesting thoughts in this one.
I sure can agree that array methods are a more readable option then loops (tradeoffs being comparative slowness and need to chain filter/map/reduce every time you need to implement something somewhat complex; but, hey, I'd say that is justified in most cases).
I can also agree that sometimes it's easier to read ternaries then "if"s (100% so when it is a simple variable assignment or some code within JSX; although you WILL have problems scaling that code for more complex logic)
BUT
creating an entire object worth of data and using a single value out of it just to avoid using a switch sounds like a bad idea when you consider performance. it might work fine for simple objects with primitives as values and just a few fields, but anything larger than that -- and you're hogging memory and considerably slowing down your code.
and as for wrapping your sync functions with promises to "simplify" error handling... I have got to say that this really seems like an absolutely terrible way to go: you're basically forcing yourself to either turn every single function in an async one or return to the promise hell that was so unintuitive to work with that async/await was introduced almost immediately. this sure does not look like a simplification to me -- not to mention performance hit it will absolutely lead to.
all in all, I think the "improvements" you are suggesting are mostly ok for smaller projects and might even sometimes help with readability, but have some serious drawbacks if you're working on large scale projects or care about your app performance.
I covered this on the post and come comments, but if you need complex logic on a ternary, that means that you need to move that logic away, avoid inlining it.
Not quite, the idea with using "dictionaries" (objects with value => value mapping) instead of
switch
/case
is to write them once and reuse them as needed (which also becomes super useful when working with i18n). About performance you can check it here.First, IDK about you, but for me at least this:
Is way more "hellish" than this:
But still, the main point with the post is to show that there isn't a single way of doing stuff, but you can use stuff like promises instead of
try
/catch
which is way closer to theMaybe
approach of functional languages. Performance might be worse (it depends, in this example, is better to use promises), but that's to be expected because you're creating a new instance ofPromise
and gaining a better API to deal with that operation that "could fail", not to mention that said API can be used with other "promised" operations. So you could chain the JSON parser of the example with aPromise.all
with a fewfetch
requests, and maybe even withnode:fs/promises
, without having to go from one API to the other (a mix oftry
/catch
with.then
/.catch
). As everything mentioned in this article, is something to consider in some scenarios, but it doesn't mean that you should use it always, just when it makes sense.Is not so much "improvements", is more like "alternatives". The important thing is not to default to one or the other. I use several of these techniques in large projects, and as a team, we had some cases where we had to evaluate if it was worth a ~3% performance improvement over having a code that would be harder to read and maintain, and we chose to keep it with the functional approach. The only time I actually had a performance issue, the problem was that the back-end was sending 10.000 elements to the front-end without any type of pagination whatsoever, so the front-end was struggling both loading that amount of data, and showing it into the DOM, but that was quickly fixed in the back-end because it wasn't a front-end responsibility at all.
Thanks for taking the time to read the article and comment!
The performance in the linked case is worse.
You just weren't waiting for the operation to execute.
See this fixed example
That was kinda the point. I mentioned that the performance will be worse by the fact that we are wrapping our action on a promise, but in the example it takes less time because multiple promises can run at the same time, which is yet another plus. If we want to await, we don't do it for every single value, we can just use stuff like Promise.all, Promise.allSettled and so on. Putting just an await in front and calling it slower is almost like adding a timeout 😅
If you're so functional, just define your own
Result
type (or import a well thought out community one).Don't abuse the native
Promise
that yields to the event loop. It's like putting every function call inside a closure insetimmediate
.The idea was to show that you can use native tools. You went from "promises aren't for that" to "but promises yield to the event loop". I get you don't want to use Promises to replace try/catch, but it has it valid usages (as I mentioned previously). You're thinking this as completely separated from other promises, but imagine it being used together with file interactions, or network interactions, and making your operations part of the then chain keeping the syntax consistent.
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more