DEV Community

Cover image for Write More Robust JavaScript: 7 Best Practices
jsmanifest
jsmanifest

Posted on • Originally published at jsmanifest.com

Write More Robust JavaScript: 7 Best Practices

Find me on medium

1. Use factory functions

If you don’t know what a factory function is, it’s simply a function (that isn’t a class or constructor) that returns an object. This simple concept allows us to take advantage of JavaScript and its features to create powerful robust applications.

It’s important to know that they’re no longer factory functions when they’re called with the new keyword.

Why factory functions?

Factory functions can be used to easily produce instances of objects without having anything to do with classes or the new keyword.

What it essentially means is that they ultimately become treated as just functions, which means they can be used to compose objects, functions, and even promises. This means you can mix and match factory functions together to create an enhanced factory function, then continue composing with other functions or objects to create even further enhanced ones. The possibilities are endless.

When we take that into consideration and combine it with good code practices, it really begins to shine.

Here is a simple example of a factory function:

function createFrog(name) {
  const children = []

  return {
    addChild(frog) {
      children.push(frog)
    },
  }
}

const mikeTheFrog = createFrog('mike')
Enter fullscreen mode Exit fullscreen mode

When you’ve used factory functions enough, you begin to realize that compared to its class constructor counterpart, it promotes stronger reusability. This results in less code, an easier time refactoring since factory functions ultimately return arbitrary objects, and an easier time managing one code to another.

2. Add methods on the .prototype when writing constructors

If you’re new to JavaScript, this section might be a little new to you as it was for me for the first two years of my experience with JavaScript.

(Keep in mind that this does not apply to classes because classes already attach methods onto their prototypes.)

Here’s an example of a constructor:

function Frog(name, gender) {
  this.name = name
  this.gender = gender
}

Frog.prototype.leap = function(feet) {
  console.log(`Leaping ${feet}ft into the air`)
}
Enter fullscreen mode Exit fullscreen mode

Why do this instead of directly attaching the leap method, like in the example below?

function Frog(name, gender) {
  this.name = name
  this.gender = gender

  this.leap = function(feet) {
    console.log(`Leaping ${feet}ft into the air`)
  }
}
Enter fullscreen mode Exit fullscreen mode

When we attach methods directly on the prototype, they get shared among all instances created by the constructor.

In other words, using the last example, if we created three separate Frogs (from this.leap = function() {...}), then we end up creating three separate copies. This is a problem because the leap method will always stay the same and doesn't need to have its own copy to its instance.

Ultimately, this results in lower performance, when it could have been avoided. The this.name and this.gender properties need to be defined on the instance because in real life, frogs probably have their own names and gender so it makes sense to have them created on the instance level.

Here's an example of this approach used by the popular request package.

3. Use the conventional .type property when differentiating

This practice works so well that it’s in extensive use today. If you’re a React developer, you’ve probably already been seeing this every day, especially when you’ve been working with Redux.

Using similar approaches also makes it extremely easy for you in your development flow since it even documents itself extremely well:

function createSpecies(type, name, gender) {
  if (type === 'frog') {
    return createFrog(name, gender)
  } else if (type === 'human') {
    return createHuman(name, gender)
  } else if (type == undefined) {
    throw new Error('Cannot create a species with an unknown type')
  }
}

const myNewFrog = createSpecies('frog', 'sally', 'female')
Enter fullscreen mode Exit fullscreen mode

4. Use TypeScript

TypeScript has become widely adopted in the JavaScript community due to its ability to provide a strong defense for type safety as well as its ability to help us catch bugs before they even occur.

Using TypeScript will enable your compiler to detect and show warnings about any potential errors in code before the code even runs.

But that’s not even close to a complete list of reasons why adopting TypeScript is good for any situation. One of the best things about TypeScript is that it allows you to use new features in JavaScript before they’re supported by major browsers since they get compiled down to earlier versions of JavaScript, ultimately being able to run in old browsers.

5. Write tests

If you’re working on a project and plan to get serious with it, it’s almost a must to employ tests so that your app becomes more predictable, less error-prone, and resilient to changes in the future. In other words, if you plan to make your project future-proof, there’s no better way than to establish tests throughout your code. The more tests you put in your code, the more confidence you’ll have when it gets deployed in production to the real world.

The best part about tests? Just the fact that that they can help you catch bugs before bugs even get a chance to occur — wouldn’t anyone want that ability? I’m certain I do! That’s why I write unit tests in my projects.

You can get started with the types of tools that are in use today by reading this post on testing frameworks and tools to get you started.

6. Keep functions as simple as possible

As we all know, in JavaScript it’s obviously possible to have huge functions that do more than one thing.

When you’re new to programming, it might have felt like a good thing — I know I felt extremely good about myself when I wrote large pieces of code that worked. It basically meant the most to me and gave me lots of confidence to see any of my code run without problems, let alone when it was humongous blocks of code that I wrote. Boy oh boy, was I extremely naive back then!

If you want to write code that’s more maintainable, simple, and less error-prone, it’s best to try to keep it as simple and small as possible. The simpler it is, the easier it is to test individually.

This is especially relevant if you’re more fond of the functional programming paradigm. It’s common knowledge that functions should do one thing and must do it well.

7. Always consider using try/catch when using JSON.parse or JSON.stringify

In JavaScript, when we pass JSON as input to the JSON.parse method, it expects a properly formatted JSON as the first argument. If it's formatted incorrectly, it will throw a JSON parse error.

The danger coming from JSON parse errors is that receiving invalid JSON crashes your app. I’ve recently been in a situation at work where one of our web projects was failing because another internal package did not wrap a JSON.parse in a try/catch. This ended up making a web page fail, and there was no way to get past this error unless the internal package fixed it. This happened because the JavaScript runtime was broken.

SyntaxError: Unexpected token } in JSON at position 107
Enter fullscreen mode Exit fullscreen mode

You shouldn’t always expect valid JSON input as it can receive weird characters like the > character, which is not uncommon today.

Conclusion

And that concludes this piece. I hope you found this to be valuable. Watch for more in the future!

Find me on medium

Top comments (11)

Collapse
 
supunkavinda profile image
Supun Kavinda • Edited
  1. Always consider using try/catch when using JSON.parse or JSON.stringify

I normally use a function with try/catch inside it so that I don't need to always add try/catch blocks.

export function decodeJSON() {
   // JSON.parse() with try/catch
}
export function encodeJSON() {
   // JSON.stringify() with try/catch
}
Collapse
 
metcoder profile image
Carlos Fuentes

Hey! What good tips! Just a few comments about

Add methods on the .prototype when writing constructors

Even I'm not a huge fan of the class syntax sugar and the use of closures to simulate OOP, I think it is better to go for the class one to attach to one standard and keep it clean instead of keeping modifying the prototype.

Keep functions as simple as possible

  • No more than 60 lines
  • The function should do one and just ONE thing.
  • Try to return something as possible (avoid void functions)
  • Don't overuse currying and/or function composition
  • Last but not least, don't over DRY

Always consider using try/catch when using JSON.parse or JSON.stringify

Here depends a lot of your app structure, It is better if you don't use try/catch every time you use it but let the error goes bubbled up until you're able to handle it properly and show a message to the user or print it to the logs.

Collapse
 
vyckes profile image
Kevin Pennekamp

These are actually good tips! I am a big fan of the factory functions myself and prefer them over classes. Good to see that one coming by for once. And I even learned something. I never knew why you would want to put functions on the .prototype, but your explanation makes sense. Definitely gonna revise some of my packages with factory functions.

Collapse
 
jsmanifest profile image
jsmanifest

I am so glad to hear that! I was hoping I would grab some readers who were stuck on that prototype mystery!

Collapse
 
brunomguimaraes profile image
Bruno Guimarães

Great article, just one thing. Isn't the first snippet supposed to be addChild(name) ?

Collapse
 
jsmanifest profile image
jsmanifest • Edited

Thank you! The intention of addChild's frog argument was that if the frog were to have children then it should have be instantiated with createFrog right before being passed in the instance. So there's the parent frog who had a baby (and the baby frog should have been instantiated with createFrog which is now also an instance of a frog sometime in our app before being passed as a child)

So in the code it would look something like this:

const mikeTheFrog = createFrog('mike')
mikeTheFrog.addChild(createFrog('william'))
Enter fullscreen mode Exit fullscreen mode

or

const mikeTheFrog = createFrog('mike')
const williamTheChildFrogOfMike = createFrog('william')
mikeTheFrog.addChild(williamTheChildFrogOfMike)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
raghavmisra profile image
Raghav Misra

Great summary! I would like to think that I'm perfect in all of these 7 practices, but I really need to start writing tests. I'll be sure to check out the link you provided. Thanks!

Collapse
 
jsmanifest profile image
jsmanifest

Your welcome!

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

Why type is not a switch I don't know, although I have never heard that this is a conversation. Nor that using prototype when a class keyword is more appropriate.

Collapse
 
jsmanifest profile image
jsmanifest • Edited

A switch here would be just fine since it just returns the result. However in a lot of situations when i'm considering switch cases I don't have to return the value evaluated from the case block which ultimately I'd become forced to put in a break which unnecessarily adds more lines than needed. In those situations it looks much nicer to just write if/else conditions while keeping the code short.

Collapse
 
metalmikester profile image
Michel Renaud

I didn't know about (2). I'll definitely keep that one in mind. Thanks for sharing!