Categories
JavaScript Best Practices

JavaScript Clean Code — Classes

Classes in JavaScript is syntactic sugar on top of the prototypical inheritance features of the language. However, in terms of writing clean code, the principles still apply since they have the same structure as classes in class-based languages.

In this article, we’ll look at how to write JavaScript classes in a clean and maintainable way.

Class Organization

Classes should begin with the constructor with a list of variable members inside.

The methods of the class can follow after the constructor and the list of variables.

Encapsulation

We should keep private variables in blocks inside the class and not as a property of this.

This way, code that’s outside the class can’t access them and change the value of them accidentally.

We should define getters and setters to get and set variables that aren’t part of this, or to use them for computed properties.

This also helps with hiding implementation so that classes won’t use them accidentally, which creates a tight coupling of our code.

Classes Should be Small

Classes should be small. They shouldn’t have more than one responsibility. What we don’t want is to have classes that do multiple things. A God class is what we don’t want.

The name of the class should tell us what responsibilities it fulfills. If a method doesn’t something that’s not covered by the name of the class, then it shouldn’t be there.

We should be able to describe what our class does without using the words ‘if’, ‘and’, ‘or’ or ‘but’.

For example, the following example of a class that has one responsibility:

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }

  get area() {
    return this.length * this.width;
  }
}

Our Rectangle class only has one responsibility and that’s to represent a rectangle.

On the other hand, if we have a createCircle method as follows, then we can’t describe our class without those conjunctions since our Rectangle class has more than one responsibility:

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }

  get area() {
    return this.length * this.width;
  }

  createCircle(radius) {

  }
}

Single Responsibility Principle

Classes should have one responsibility and one reason to change. This principle gives us a good guideline on a class’s size. If it has more than one responsibility than it’s too big.

Identifying responsibilities let us create better abstractions in our code. We should move the createCircle method to its own class Circle since circles have nothing to do with rectangles.

So we can write it as follows:

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  get area() {
    return Math.PI * (this.radius ** 2);
  }
}

The single responsibility principle is one of the more important principles of object-oriented design. It’s also very easy to understand.

Unfortunately, it’s also one of the more principles that’s ignored. People just get their programs to work and forget to clean them up.

They move on to the next problem once they get their code working and just didn’t bother to clean it up.

Some people are also worried that making smaller, single-responsibility classes make it more difficult to understand the big picture. They think that navigating from class to class makes it harder to get the full picture of the system.

However, this isn’t true since they have the number of moving parts. If we put everything in one class, then we still have to dig for them inside the class rather than in different files. It’s no different.

Smaller classes have less code in it so it’s easier to read them.

Cohesion

Classes should have a small number of instance variables. Each method should manipulate one or more instance variables. A class where each variable is used by each method is maximally cohesive.

We like cohesion to be high so that the methods and instance variables are co-dependent and stay together as a whole.

High cohesion makes reading the code easy since it only revolves around a single concept. They’re also less frequently changed since each class doesn’t do much.

For example, our Circle class:

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  get area() {
    return Math.PI * (this.radius ** 2);
  }
}

is cohesive because we used our radius instance variable in the area getter method, so we used every instance variable in our method.

Maintain Cohesion Means Many Small Classes

As we can see, it’s easy to create cohesive classes that are small. They have fewer instance variables so it’s easy to use them all in methods.

Bigger classes have problems maintaining cohesion because we keep adding new instance variables that only a few methods use.

This means that when classes lose cohesion then we should split them up. So instead of writing:

class Shape {
  constructor(radius, length, width) {
    this.radius = radius;
    this.length = length;
    this.width = width;
  }

  get circleArea() {
    return Math.PI * (this.radius ** 2);
  }

  get rectangleArea() {
    return this.length * this.width;
  }
}

We should instead split them into the Rectangle and Circle class since the instance variables and methods make no sense being together.

So it’s better to write:

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }

  get area() {
    return this.length * this.width;
  }
}

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  get area() {
    return Math.PI * (this.radius ** 2);
  }
}

Conclusion

JavaScript classes should follow the clean code principles, even though it’s only syntactic sugar on top of its prototypical inheritance model.

We should have cohesive classes where methods use all the instance variables defined.

Also, each class should have a single responsibility. This and cohesion makes reading the code easy since they only revolve around a single concept. They’re also less frequently changed since each class doesn’t do much.

Small classes are better than large classes since they don’t have more than a single responsibility and they are more cohesive.

Categories
JavaScript Best Practices

JavaScript Clean Code — Test-Driven Development

With software as complex as it is today, we can’s rely on manual testing to for every part of it. This is where unit tests come in.

One way to develop software is to write the tests first before writing the production code. This is called test-driven development or TDD.

In this article, we’ll take a look at what test-driven development is in detail and how we write tests along with production code.

Three Laws of TDD

There’re 3 principles of TDD:

  • We may not write production code until we’ve written some failing unit tests.
  • We only write a test that fails and not makes compilation fail.
  • We write production code to make the failing test pass.

This means that the test and code are written together. The result is that we write dozens of tests every day and test coverage would be comprehensive.

Keeping Tests Clean

Test code should be kept to the same quality as normal production code. This way, it’ll be easy to maintain the test.

Things like naming things properly, creating functions that aren’t too long, formatting, etc., all apply to unit tests just as much as production code. This makes tests easy to maintain so people can move on to writing production code.

It requires just as much care in terms of design and implementation as production code.

If we don’t maintain our tests to the same quality as the production code, we lose flexibility in our production code since we don’t have confidence in our production code without the tests. We’ll fear of making changes because we don’t have tests to tell us that our production code isn’t creating bugs.

Clean Tests

Clean tests are readable. It’s the most important part of unit tests because we have to look at them and know what they are testing.

Readability includes clarity, simplicity, and density of expression. We want to say a lot with a few expressions as possible.

Each test should follow the build-operate-check pattern. This means that we should set up our mocks if needed and set any real data, then we do something with them by calling the code that we’re testing, then we check for the expected result.

Dual Standard

Since test code runs in a test environment only, they don’t have to be as efficient as production code. However, they do have to be fast so that we don’t have to wait too long to run all the tests since they’re going to be run as we develop the code.

One Assert Per Test

To make tests as granular as possible, it’s a good idea to have only one assert statement per test.

This makes our tests simple and we can change them more easily. It’s also more clear of what we’re checking.

Single Concept Per Test

A single concept is a better rule for unit tests. Tests are used to check one concept so everyone knows what each test is checking. There’re no surprises or hidden code.

FIRST

FIRST is an acronym for the principles for writing tests. Each letter stands for one principle. They’re as follows:

  • F for fast — tests should run fast. Slow tests are torture to run, so we won’t want to run them frequently. If we don’t run them frequently then we’ll miss regressions until we catch them later. Also, we’ll be less comfortable with code changes since we can’t verify the results by running tests.
  • I for independent — tests shouldn’t depend on each other. They shouldn’t set up the conditions for the next test. This is because when one fails, then all the other fails, making diagnosis hard
  • R for repeatable — tests should be repeatable in any environment. It shouldn’t matter if there’s a network connection or not. So for these kinds of things, we should mock them. We’ll run into problems when tests depend on external resources that might not always be available.
  • S for Self-validating — tests should have boolean output, either they pass or fail. We shouldn’t have to read through logs to tell if tests pass. Failure becomes subjective otherwise and they require long, manual evaluation
  • T for Timely — tests need to be written in a timely fashion. They should be written before the production code to make them pass. We might run into production code that’s hard to test if we write tests after.

Conclusion

Test-driven development is something we should consider when writing our code. We can write tests before production code to make sure there’s coverage for everything and that production code is easy to test.

Each test should run fast and are small. They should stick to testing single concepts and do it independently of other tests.

Tests should run in any environment the same way. They shouldn’t rely on external resources that aren’ t always available.

Test results should either be pass or fail. It shouldn’t be up for subjective interpretation by people.

Finally, the test code has to be as clean as production code so that we can read and maintain them easily.

Categories
JavaScript Best Practices

JavaScript Clean Code — Bad Comments

Comments are sometimes using to explain our thoughts which can’t be explained with code. Other times, it’s used to explain what some messy code is doing.

In this article, we’ll look at various types of bad comments.

Most comments are bad comments. They’re used to explain bad decisions or bad code. We should avoid these kinds of comments.

Mumbling

Adding comments just because we feel like it or the process requires it isn’t a good reason to put in comments.

At least the comments have to be useful if we’re to write them. For example, if we have:

try {
  loadProperties();
} catch (e) {
  // No properties means everything is loaded
}

Then we have a comment that isn’t very useful in the catch block. We have no idea what the author is trying to say.

We don’t know what ‘properties’ mean. When is everything loaded? It just doesn’t tell us too much useful information.

It probably confuses more people that help them. We probably end up just looking at the code to see what the loadProperties function does to gain knowledge of the code.

Redundant Comments

These are comments that explain the same thing that is already explained by the code.

For example, if we have:

// adds 2 numbers;
const add = (a, b) => a + b;

Then the ‘adds 2 numbers’ comments are redundant because we already know that the function is called add and it takes 2 numbers and returns the sum of them. Therefore, we don’t need a comment before it to explain what’s already shown in the code.

Misleading Comments

Misleading comments lead developers that are working on a piece of code to have the wrong understanding of the code and make bad decisions and create bugs.

Thus obviously isn’t food. It leads to more time debugging to find out why something won’t work as they described in the comments.

Mandated Comments

If a piece of code isn’t for the public to use, then there’s no read to have comments for them since we don’t need to generate documentation for them.

It just clutters up the code file with extra text that doesn’t do much to help readers and other developers.

Journal Comments

Writing a long comment with the log of all the changes to a piece of code is useless. They’re long and don’t provide much information since we already have the change history in our version control systems.

If we aren’t using version control to version our code, it’s time to use it now so we can log the change history, create copies with branches and merge different people’s code together into one as a team of people works on it.

Noise Comments

Noise comments are nothing but noise. They’re useless and just take up space on the screen and on disk.

We go back to this example:

// adds 2 numbers;
const add = (a, b) => a + b;

This is obviously not very useful since the code already explains everything the same way as the comments.

Don’t Use Comments when Identifiers Can be Named Better

We can easily write code that explains themself. For example, instead of writing:

// check if employee is eligible for child care benefits
if (employee.salary < 50000 && employee.hasChild) {
  //...
}

we can write:

const isEligibleForChildCareBenefits = (employee) => {
  return employee.salary < 50000 && employee.hasChild;
}

if (isEligibleForChildCareBenefits(employee)) {
  //...
}

Our new function explains the same thing as the comments, but with fewer words.

Comments also get outdated fast since they’re often neglected when the code is being changed.

Position Markers

Some developers like to put position markers like the following:

// //////////

The use of these extra comments is questionable most of the time. Only use them if they’re actually important.

Closing Brace Comments

Some people like to mark lines with closing braces with a comment as follows:

if (employee.salary < 50000 && employee.hasChild) {
  //...
} // if

This is stating the obvious and so it’s not that useful. Most text editors and IDEs highlight the opening and closing braces anyways when your cursor it over it so we won’t miss them.

Attributions and By-Lines

This is something like:

/* Added by Bob */

Source control systems provide this information already, so we definitely don’t need them in our code.

Commented-Out Code

Commented our code clutters up our files and screen. Also, it doesn’t provide any value since they’re commented out. It also leads developers to read the code to question why they’re commented out in the first place.

It’s easy to mislead developers with them, so they should be removed. If the program still works with them commented out, they don’t need to be in the code.

If we need them back, we can get them back from the source control system’s change history.

HTML Comments

HTML comments definitely don’t belong in code. Humans can’t read them. Therefore, it should be the documentation program’s responsibility to generate HTML code from our human-readable comments.

Too Much Information

Nobody wants to read an essay in the comments, so we shouldn’t write one. Better just to keep it short if we need them at all.

JSDoc in Nonpublic Code

Code that isn’t exposed to the public doesn’t need to be documented like they’re made for the public. So we shouldn’t do it and save ourselves lots of time documenting code that shouldn’t be documented.

Conclusion

As we can see, there’re many types of bad comments. We should avoid all of them to save our own and other developers’ time. They don’t contribute any value or they mislead people, which is even worse.

Categories
JavaScript Best Practices

JavaScript Clean Code — Function Arguments

Functions are important parts of JavaScript programs. They are used for dividing code up into reusable chunks. Therefore in order to have clean JavaScript code, we have to have easy to understand functions.

In this article, we’ll look at more characteristics of good functions, including reducing the use of switch statements, reducing the number of parameters in our functions, and getting arguments in an easy to understand way.

Switch Statements

switch statements are always long even if they have only a few cases. Therefore, they should be used in a way that’s never repeated by separating into a function that can be invoked wherever is needed.

An example of a bad switch statement would be something like the following code:

const calculatePay = (employeeType) => {
  switch (employeeType) {
    case 'PART_TIME':
      return calculatePartTimePay();
    case 'FULL_TIME':
      return calculateFullTimePay();
    case 'COMMISSIONED':
      return calculateContracorPay();
    default:
      return 0;
  }
}

The code above is long and it does more than one thing. It’s better to just skip the switch statement and then call them wherever they’re needed.

Also, we have to change this function every time we change the type.

Another problem is that we’ll have many other functions with this structure, which means the code will get longer and have more functions with the same problems as this one.

To deal with this, we can return the correct object for the type of employee that we want. For example, we can write instead:

const getEmployeeObjectByType = (employeeType) => {
  switch (employeeType) {
    case PART_TIME:
      return new PartTimeEmployee();
    case FULL_TIME:
      return new FullTimeEmployee();
    case CONTRACTOR:
      return new ComissionedEmployee();
    default:
      return undefined;
  }
}

And then we can just call this function to get the right type of employee and then do what we want with it.

It’s much better than having multiple functions with the same switch statement cases doing different things.

Descriptive Names

Like any other identifier in our code, the names in functions should be descriptive like everything else.

They should be named with names that tell what they mean. Information like the meaning, intent, and action should be conveyed from the code.

The type may also be helpful in some cases if the type of something isn’t immediately clear from looking at the code since JavaScript is a dynamically typed language.

Longer names are OK is they’re needed to convey all the information that we need to know. They can still be easy to read if they follow JavaScript’s convention, which is to use camel case for variables and non-constructor functions, upper case for constants, and upper camel case for constructor functions.

Function Arguments

Functions should have as few parameters as possible. This is because we have to look at all of them and when we call them, we have to make sure that they’re all passed in.

Therefore, if we don’t need parameters, then we shouldn’t include them. Zero is better than one and one is better than 2.

More than 5 is probably too much unless there’s some special reason for it.

Passing in arguments is hard since we have to gather all the variables and values and pass them in. Likewise, it makes testing harder for the same reason.

When we test a function with lots of arguments, we’ve to test all the possible combinations of arguments to get full test coverage. This makes testing a function that takes lots of arguments many times harder than ones that take fewer arguments.

Also, with the rest operator being a standard feature in JavaScript, it’s time to abandon the arguments object if we need to define a function with lots of arguments. It’s an array-like object so it has some property of arrays, like indexes and the length property.

We can also loop through it with a for or a while loop. However, it doesn’t have any array methods that are part of a normal array.

Like other array-like objects, it can be spread with the spread operator.

They just create confusion for lots of people since many people aren’t familiar with the arguments object.

The arguments object also doesn’t bind to arrow functions, so we can’t use it with them.

With the rest operator, we get an array of arguments returned from it, so we can do everything that we can do with arrays.

So instead of writing:

function add() {
  return [...arguments].reduce((a, b) => a + b, 0);
}

We should write:

function add(...args) {
  return args.reduce((a, b) => a + b, 0);
}

To make a function that adds some numbers together. Or better yet:

const add = (...args) => {
  return args.reduce((a, b) => a + b, 0);
}

since we aren’t using this in our code.

As we can see, the 2 later examples with the rest operator are much clearer than the first one. The first one has no parameters in the signature and yet we’re getting the arguments passed in from the arguments object.

The other 2 shows that our add function actually takes arguments and we’re actually doing something with them.

Conclusion

Functions might look easy to define on the surface, but to define functions that are easy to read and change, we have to do it with care. We should avoid having too many switch statements.

If we do need them, we should abstract them so that one switch statement can be used in multiple places. They’re long and should be minimized.

Also, the names of things inside a function should be descriptive, and we should never take too many arguments in our functions. If we do, use the rest operator instead of the arguments object to get the arguments passed into our functions.

Categories
JavaScript Best Practices

JavaScript Clean Code — Function Parameters and Side Effects

Functions are an important part of JavaScript programs. They’re used for dividing code up into reusable chunks. Therefore in order to have clean JavaScript code, we need to have easy to understand functions.

In this article, we’ll look at more properties of good functions, including flag arguments, dyadic and triadic functions, and side effects.

Flag Arguments

Boolean parameters should be used sparingly. It makes the function signature more complex and it tells us that the function does more than one thing (has multiple paths).

Dyadic Functions

Dyadic functions are harder to understand than functions that take fewer arguments. However, sometimes they make sense. For example, if we have an object that holds the Cartesian coordinate, then it should take 2 arguments.

For example, we can have a class with a constructor that takes 2 arguments as follows:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

const point = new Point(1, 2);

It’s pretty much impossible to define it any other way.

However, we do have to be aware that it takes more time and brainpower than functions that take fewer arguments.

Triadic Function

Functions with 3 arguments take a lot of time and brainpower to understand that functions that take 2 arguments.

There’re many more combinations of arguments to think about that if there’re 2 or fewer arguments.

Combining Arguments into Objects

If a function takes many arguments, we should consider combining them into objects.

This is especially true if they’re related. For example, the following function takes many parameters:

const describeFruit = (color, name, size, price, numSeeds, type) => {
  return `${fruitName} is ${fruitColor}. It's ${fruitSize}. It costs ${price}. It has ${numSeeds}. The type if ${type}`;
}

6 parameters is probably too many. We can clean this up by passing in an object instead:

const describeFruit = (fruit) => {
  return `${fruit.name} is ${fruit.color}. It's ${fruit.size}. It costs ${fruit.price}. It has ${fruit.numSeeds}. The type if ${fruit.type}`;
}

As we can see, it’s much cleaner. We don’t have to worry about passing in many arguments.

It also fits better on the screen since it’s shorter.

5 parameters are probably the maximum that should be in a function.

Verbs and Keywords

It’s a good idea to include verbs and keywords into our function names since they do something, which means an action word in the name is justified.

Also, we need to know what things we’re applying the action to. This means that we have to add some keywords to do that.

For example, a good function definition that meets this rule would be something like:

const copyArray = (array) => [...array];

The copyArray name let us know that our function makes a copy of an array.

It also lets us know what we’re passing into the function, and that’s clearly an array.

No Side Effects

Side effects are code in a function that makes changes to things that are outside the function.

This isn’t good because it’s making hidden changes to things that are outside the function.

We should avoid this as much as possible since it does something unexpected and also it takes testing harder because, in addition to taking in arguments, doing things and returning a result, it also makes changes to something outside the function that we have to account for.

This means that we have to test things outside of what the function returns.

For example, if we have:

let numFruits = 1;
const addFruit = () => {
  numFruits++;
}

const removeFruit = () => {
  numFruits--;
}

Then we have 2 functions with side effects because they’re both changing the numFruits variable which is outside each function.

A better way to write these functions is to write them as pure functions. Pure functions are functions that returns the same thing given the same arguments are passed in. Also, it has no side effects.

Pure functions are easier to test because of that and the behavior of them is also predictable.

We can rewrite the code above by writing them as follows:

let numFruits = 1;
const addFruit = (numberOfFruits) => numberOfFruits + 1;
const removeFruit = (numberOfFruits) => numberOfFruits - 1;

numFruits = addFruit(numFruits);
numFruits = removeFruit(numFruits);

We now have 2 functions that take in one numFruits parameter and return a number that’s one bigger or smaller respectively.

Then we can use them to change the numFruits variable that we have outside the functions.

As we can see, they do nothing to the numFruits but rather it returns the numberOfFruits parameter plus 1 or minus 1 respectively.

If we write tests for them, then we can test them easily by passing in the input and checking if the output is what we want. This is much better than committing side effects to a variable that might be available to the test code.

Conclusion

Flag arguments should be minimized. They tell us that the function can do more than one thing and it’s another parameter in the function signature.

Functions that take fewer arguments are better than the one that takes more. If it takes lots of arguments, consider combining them into a single object.

Finally, side effects should be avoided if possible. Functions with side effects do hidden things and are hard to test. Pure functions are much more testable and more predictable since they don’t commit side effects.