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: