In this post I get into some patterns I try to use while programming. These patterns are observations I've made about myself recently while working as well as a couple I stole from coworkers over the years.
These patterns are in no particular order just a simple collection.
1. Early exits
function transformData(rawData) {
// check if no data
if (!rawData) {
return [];
}
// check for specific case
if (rawData.length == 1) {
return [];
}
// actual function code goes here
return rawData.map((item) => item);
}
I call this pattern 'early exits' but some also refer to this as 'the Bouncer Pattern' or 'guard clauses'. Naming aside, this pattern takes the approach of checking for invalid use cases first and returning out from that function otherwise it continues onto the expected use case of the function and executes.
For me, this approach has some positives that I really like:
- encourages thinking around invalid/edge cases and how those cases should be handled
- avoids accidental and unnecessary processing of code against an unexpected use case
- mentally allows me to process each use case much more clearly
- once adopted, you can quickly glance at functions and understand the flow and execution which typically follows a top down approach going from - invalid cases -> small cases -> expected case
More info:
2. Switch to object literal
// Switch
let createType = null;
switch (contentType) {
case "post":
createType = () => console.log("creating a post...");
break;
case "video":
createType = () => console.log("creating a video...");
break;
default:
createType = () => console.log('unrecognized content type');
}
createType();
// Object literal
const contentTypes = {
post: () => console.log("creating a post..."),
video: () => console.log("creatinga video..."),
default: () => console.log('unrecognized content type')
};
const createType = contentTypes[contentType] || contentTypes['default'];
createType();
Next up is removing the switch
. I often make mistakes when writing each case
and very often forget a break
. This causes all kinds of fun issues. The switch
statement doesn't add a whole lot of value when I'm writing code. It seems to get in the way.
I prefer using an object literal instead, here's why:
- don't have to worry about
case
orbreak
- easier to read and quickly understand what's happening
- object literals are easy enough to write
- less code
More info:
- Switch case, if else or a loopup map by May Shavin
- Replacing switch statements with object literals by Todd Motto
- Rewriting Javascript: Replacing the Switch Statement by Chris Burgin
3. One loop two arrays
const exampleValues = [2, 15, 8, 23, 1, 32];
const [truthyValues, falseyValues] = exampleValues.reduce((arrays, exampleValue) => {
if (exampleValue > 10) {
arrays[0].push(exampleValue);
return arrays;
}
arrays[1].push(exampleValue);
return arrays;
}, [[], []]);
This pattern is nothing really special and I should have realized it sooner but I found myself filtering a collection of items to get all items that matched a certain condition, then doing that again for a different condition. That meant looping over an array twice but I could have just done it once.
Turns out this has a name (bifurcate) and I stole it from 30secondsofcode.org. If you've never checked out that site I suggest going there. So much good information and useful code.
I know reduce can be kind of daunting and not very clear what is going on but if you can get comfortable with it, you can really leverage it to build any data structure you need while looping over a collection. They really should have called it builder
instead of reduce
.
More info:
4. No 'foo' variables
// bad
const foo = y && z;
// good
const isPostEnabled = isPost && postDateValid;
This one may seem kind of obvious but I'm sure we all have seen code that does this. Take the time and do your best to name something appropriately.
This is especially important for working professionals or people who are in a position where they are educating others. Variable naming should be used to help explain and give context to what is going on within the code.
Someone should be able to read your code and loosely begin to understand what is trying to be solved.
More info:
5. Nested ternaries
let result = null;
if (conditionA) {
if (conditionB) {
result = "A & B";
} else {
result = "A";
}
} else {
result = "Not A";
}
const result = !conditionA
? "Not A"
: conditionB
? "A & B"
: "A";
I'll admit, in the beginning the idea of nesting ternaries was off-putting. It just seemed like a clever way to write conditionals. Then I started writing business logic and found myself with nested if else clauses and some pretty questionable conditional logic.
I think if
and else
are much easier to read as they are actual words but when these become nested I start to really have a hard time following what is going on and mentally keeping track of everything.
I started deferring to ternaries and nested ternaries and I found I was able to quickly understand at a glance what was happening.
I think this pattern is really up to you and your team and your preferences. I have worked in codebases that do both well and can see both sides to this, but personally nested ternaries are really growing on me.
More info:
Top comments (59)
Personally, my favorite way to do JavaScript switches is to isolate them to their own function. If you're doing a simple dispatch, a function is already an appropriate abstraction, and
return
gives you the ability to avoid thebreak
mess.Utilizing the switch statement also dodges the biggest drawback of the object literal: the location of the object. If you put the object literal in your dispatch function, you're creating the object every time you run the function. If you create the object once outside of the function, now your dispatch logic is in a different place than the actual dispatch call.
I wish JavaScript had a built in pattern matching construct, but a well used switch statement isn't all that bad.
Example with no object creation in the function:
I love this solution, very simple;
I usually do this:
This is how redux architecture works ;)
I like that
switch
versus the one I wrote. :DI recently discovered pattern matching via Scala and enjoyed using it!
Why not wrap the whole thing in one function. Use it as a closure to store the type data then return the switch function to access it?
Cool post ! :)
Love seeing these posts! I just recently used a switch in my code and didn't like the overall bulk the switch created. Nice ideas on how to clean it up!
As a personal preference I just try to avoid nesting in any form as much as possible because it really obscures the logic.
Say, this code snippet,
into:
or even:
Everything else looks fine but the nested turn ops.. not for me, clarity can go a long way over terse for the sake of others. Besides there are alternatives to nested conditionals. Boolean array .every or .some spring to mind.
Clever! Gonna remember this one
Your most welcome 😄, this is functional programming at it's finest. The only thing I don't like is the need to return the result by lambda. I haven't tried it but perhaps the
Boolean
constructor could sit whereisTrue => isTrue
is currently? I am afk so can't test.This sentence went "swoosh" over my head.
I also had to google what a lambda was - had never heard that name for an anonymous function before (but I'm an autodidact which could explain it..) 😂
Could you elaborate in an ELI5 way? :D
3. One loop two arrays
Is reduce really necessary here? Wouldn't it be so much clearer to do this with a
forEach
?5. Nested ternaries
The same flow you used in the ternaries can be applied to the
if
statements:And if you used the same flow as in your
if
statements but with ternaries, they'd also look weird:Also, I find ternaries formatting to be more readable this way:
I think reduce is nicer because it's always clear that an
Array.reduce
constructs "something" from an array. The whole statement includes everything you need to know (i.e. the final argument is the base value) as opposed to declaring the arrays before the loop. No need to look outside the reduce for answers since it's all there.Reduce can be a little intimidating in the beginning but once you get the hang of it it's wonderful and makes a lot of sense.
So it's better because you can put it in a single statement, even though you now have to address the arrays as
array[0]
andarray[1]
instead of by name?You don't! You could actually do this instead:
I've spread the array in the return statement because I wasn't too fond of the push along with having to return the two arrays.
Another one, using an object instead of two arrays would be:
Which in my opinion is probably the easiest one to read.
Which is how
reduce
is meant to be used, and why it is needed in functional languages that deal with immutable data. Otherwise you are just abusing it as a loop construct to feel functional.But I wouldn't recommend this style in Javascript, because arrays are not linked lists and this becomes O(n2).
I don't have a computer science background so what you're referring to is not something I can easily relate to. I have a vague idea of the concept of linked lists, but I don't understand the big O notation (entirely new concept to me).
Do you mean that it's less performant than needed? Because I'm creating a new array which contains two arrays both of which will have to be looped due to using the spread operator as opposed to a push (which would not need to loop the whole array)?
Sorry if I'm not very clear, I'm simply not very good with these concepts and I'm genuinely interested in understanding what you're saying.
Update: Ok, I think I understand now.. You're saying that my solution with the spread operator will decrease in performance for each value in the exampleValues array, which is a bad practice. And it's O(n2) (opposed to O(n)) because I'm doing it twice?
O(n2), not O(n2). As in - O(n*n). As the array get bigger, the spread operator takes more and more time to perform because it needs to copy more values, as opposed to
push
which only needs to add one value (and sometimes increase the size, which takes longer - but it doesn't do it on eachpush
)I'm aware, I just don't know how to do it on my keyboard. And yes, I do realise there's a major difference and it shouldn't be trivialised..
Anyway, thanks - I learned something new today :)
dev.to does it for you with the
^
operator - soO(n^2)
becomes O(n2).HA! I posted the almost the exact same else-if logic before realizing you had!
Personally, I don't like the nested ternary conditions.
Instead of the nested
if..else
, I would prefer the "Early assignment" like "Early Exits"Nice post!
Re #1: Rubocop calls them "guard clauses" and I tend to prefer that style as well.
But if there are only two branches, I sometimes prefer the symmetry of
if / else
. Especially whenif
is an expression (as it is in Ruby).Either way, IMO it's usually clear enough when input validation is being done, however it gets written. It's one of those places where obscuring the intent takes real talent :P
Re: #2: agree with @nepeckman that putting the switch statement in a function is usually a good option. Especially if multiple cases map to the same output value, because there you can use switch fallthrough to "stack" the tests and avoid repeating yourself.
Re: #3:
reduce
(fold) is so powerful 😍...map
andfilter
can both be written in terms of it, and many more things as well. Many "indexed" loops can be translated into a function folded over a range of integers.Also, I haven't seen it formalized, but it seems like a lot of tail-recursive code can be translated to "reducer style" as well -- it's a good option if the inputs can be easily generated as some sort of collection.
If the fold implementation allows early termination (a la Clojure's reduced, it becomes even more useful.
Oh yeah, in case you haven't seen it in action, you might be interested to know that a side-effect-free series of
map
calls can be refactored similar to your #3. map distributes across function composition, soThe latter form only walks the collection once, obviously. You can compose the functions manually or borrow
compose
from a library like Ramda.Part of our coding standard is no acronyms (with a very few specific exceptions for industry terms). It is amazing how much easier it is to read code from 5+ years ago when it isn't littered with little three letter shibboleths.
I think nesting ternaries makes things more difficult to change later without scratching your head. The linked article says it simplifies the conventional
if
but it doesn't go all the way:
At this point the
if
and the ternary are nearly identical, with the exception that theif
statement blocks can contain anything they like without breaking the layout. Imagine if instead of returning a simple value they were all returning a long string or a calculation? I mean, you can say that these sorts of things should be factored into their own functions, but that's true of both techniques.There is a very unfortunate drawback of using a function for "if" or ternary... both branches are always evaluated and that often means dereferencing null or undefined...
Early exits keep indentation sane which contributes a lot to readability.
Nested ternaries are great and very clear if you lay them out like:
so the structure of each line is exactly the same.
(Don't try this in PHP by the way - you need parens to fix the broken associativity).
You're right. I have found it is exactly easy to understand the ternaries because the priority level is default satisfying what I want it to be. But
One loop two arrays
I don't agree. I code the following code and it is more elegant: