DEV Community

Cover image for 5 Programming Patterns I Like
John Stewart
John Stewart

Posted on • Originally published at johnstewart.dev

5 Programming Patterns I Like

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 or break
  • easier to read and quickly understand what's happening
  • object literals are easy enough to write
  • less code

More info:

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:

Latest comments (60)

Collapse
 
myterminal profile image
Mohammed Ismail Ansari

It's very relatable, have been doing almost all of them in my code (except the two arrays pattern, which is a great idea as well).

Collapse
 
marclundgren profile image
Marc Lundgren • Edited

When using reduce, I prefer to always return a new pure array rather than pushing to an existing array. Keeping reduce stateless has resulted in less debugging for me.

const exampleValues = [2, 15, 8, 23, 1, 32];
const [truthyValues, falseyValues] = exampleValues.reduce(([truthy, falsey], value) => {
  if (value > 10) {
    return [truthy.concat(value), falsey];
  } else {
    return [truthy, falsey.concat(value)];
  }
}, [[], []]);
Collapse
 
zegio profile image
Tony

I really appreciate and identify with this article.

But... Not the nested ternaries. The example used as the "bad nested if" feels to me like those commercials for cracking eggs and how no one can do it (meaning, it's actually easy, but we want to sell you a thing)

First, let's kill the unneeded semicolons, because, yuck. Second, reverse the first logic and use else if for the second and you have yourself some pretty simple logic that can also be multiple lines long... but... for the sake of this example we don't need curly braces either. So apples to apples if code would be below. Honestly, the nested ternary functions I've seen are a HUGE mess, not at all as simple as the example provided. Especially while returning JSX and returning blocks of JSX-HTML...shudder

if (!conditionA)
  result = 'Not A' 
else if (conditionB)
  result = 'A & B'
else
  result = 'A'

Multi-line Version

if (!conditionA) {
  result = 'value: Not A' 
  console.log('not A')
} else if (conditionB) {
  result = 'A & B'
  console.log('value: A & B')
} else {
  result = 'A'
  console.log('value: A')
}
Collapse
 
shlommi profile image
shlommi

hi,
someone can give me advice which book about programming patterns to buy ? I'm interested in this.
thanks

Collapse
 
kiransiluveru profile image
kirankumar • Edited

For nested ternaries we can use self-executable loops:

{ (() => {
if(x) {
//do something...
console.log(''x')
}
if(y) {
//do something...
console.log(''x')
}
if(z) {
//do something...
console.log(''x')
}
}
)()
}

Collapse
 
webgodo profile image
Mohsen

Such an incredible idea is in 'bifurcate'

Collapse
 
assertnotnull profile image
Patrice Gauthier

Instead of switch to object literal I suggest the facade pattern. Going with facade pattern, your IDE will be able to resolve function definition. If this object resolution grows in complexity it will be harder to understand why this generic function refers to and only your debugger can help you.

Collapse
 
booradley profile image
boo radley

The details of this would be an interesting topic for a follow-up article.

Collapse
 
moonpatroller profile image
moonpatroller

Omitting the else and else ifs when you do the early return always seems dangerous.

Collapse
 
xtofl profile image
xtofl • Edited

These patterns are so useful in day to day work!

In the 'one loop two arrays' pattern for partitioning I like to emphasize what is the essential, minimal difference in each condition. In this case, it's really just 'what array do I append to?'

var target =
  arrays[
    predicate(example) ? 1 : 0];
target.push(example);
Collapse
 
geompse profile image
Geompse

Nested ternaries can be tricky for their operator not having the same priority between programming langages :

a='a',b='b',c='c',d='d',e='e',a?b:c?d:e; // ==='b'
$a='a';$b='b';$c='c';$d='d';$e='e';echo $a?$b:$c?$d:$e; // ==='d'

Also using spaces or multiple lines may mislead the programmer, suggesting priority :

a?b:c ? d : e;
a?b:c
  ?
    d
  :
    e;

To me, it is always better to use parenthesis, on one line of code. If the line goes too long you should use variables (as in pattern 4 "No 'foo' variables") :

const result1 = !conditionA ? "Not A" : (conditionB ? "A & B" : "A");
const result2 = (!conditionA ? "Not A" : conditionB) ? "A & B" : "A";

Still my preference goes to a non nested if/elseif/else blocks (i find it a lot clearer) :

if (!conditionA) {
  result = "Not A";
else if (conditionB) {
  result = "A & B";
} else {
  result = "A";
}
Collapse
 
denishowe profile image
Denis Howe

Early exits keep indentation sane which contributes a lot to readability.

Nested ternaries are great and very clear if you lay them out like:

const result =
  !conditionA ? "Not A" :
  conditionB ? "A & B" :
  "A";

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).

Collapse
 
vzuburlis profile image
Vasilis Zoumpourlis

The Early Exits I know them as "Quick Fails" and its the best patter I learned recently.

Collapse
 
thejohnstew profile image
John Stewart

I like that name also

Collapse
 
tremainebuchanan profile image
Tremaine Buchanan

Great post! We both use the first concept you mentioned in the post. I'll explore the others and see how I can incorporate them also.

Collapse
 
thejohnstew profile image
John Stewart

Thanks!

Collapse
 
idanarye profile image
Idan Arye

3. One loop two arrays

Is reduce really necessary here? Wouldn't it be so much clearer to do this with a forEach?

const exampleValues = [2, 15, 8, 23, 1, 32];
const truthyValues = [];
const falseyValues = [];
exampleValues.forEach(exampleValue => {
        if (exampleValue > 10) {
            truthyValues.push(exampleValue);
        } else {
            falseyValues.push(exampleValue);
        }
});

5. Nested ternaries

The same flow you used in the ternaries can be applied to the if statements:

let result = null;
if (!conditionA) {
    result = "Not A";
} else if (conditionB) {
    result = "A & B";
} else {
    result = "A";
}

And if you used the same flow as in your if statements but with ternaries, they'd also look weird:

const result = conditionA
    : (
        conditionB
        : "A & B"
        ? "A"
    )
    ? "Not A";

Also, I find ternaries formatting to be more readable this way:

const result =
    !conditionA ? "Not A"
    : conditionB ? "A & B"
    : "A";
Collapse
 
adamgerthel profile image
Adam Gerthel

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.

Collapse
 
idanarye profile image
Idan Arye

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] and array[1] instead of by name?

Thread Thread
 
adamgerthel profile image
Adam Gerthel

You don't! You could actually do this instead:

const [truthyValues, falseyValues] = exampleValues.reduce(([truthyValues, falseyValues], exampleValue) => {
  if (exampleValue > 10) {
    return [[...truthyValues, exampleValue], falseyValues];
  }

   return [truthyValues, [...falseyValues, exampleValue]];
}, [[], []]);

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:

const { truthyValues, falseyValues } = exampleValues.reduce((result, exampleValue) => {
  if (exampleValue > 10) {
    result.truthyValues.push(exampleValue)
  } else {
    result.falseyValues.push(exampleValue)
  }

   return result
}, { truthyValues: [], falseyValues: []});

Which in my opinion is probably the easiest one to read.

Thread Thread
 
idanarye profile image
Idan Arye

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.

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).

Thread Thread
 
adamgerthel profile image
Adam Gerthel • Edited

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?

Thread Thread
 
idanarye profile image
Idan Arye

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 each push)

Thread Thread
 
adamgerthel profile image
Adam Gerthel

O(n2), not O(n2). As in - O(n*n)

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 :)

Thread Thread
 
idanarye profile image
Idan Arye

dev.to does it for you with the ^ operator - so O(n^2) becomes O(n2).

Collapse
 
zegio profile image
Tony

HA! I posted the almost the exact same else-if logic before realizing you had!

Collapse
 
karuppasamy profile image
Karuppasamy M

Personally, I don't like the nested ternary conditions.

const result = !conditionA
  ? "Not A"
  : conditionB
  ? "A & B"
  : "A";

Instead of the nested if..else, I would prefer the "Early assignment" like "Early Exits"

let result = null;
if (conditionA) {
  if (conditionB) {
    result = "A & B";
  } else {
    result = "A";
  }
} else {
  result = "Not A";
}

// instead

let result = "";

if(!conditionA) result = "Not A";
if(!conditionB) result = "A"
result = "A & B"