Categories
JavaScript Best Practices

JavaScript Clean Code — Default Parameters, Properties, and Singletons

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at how we can use them to make our code easier to read and write.

ES6 Default Parameters

We can use default parameters syntax to set the default values of parameters.

If the argument is omitted or undefined, then the default parameter value will be set as the value of the parameter.

For instance, we can use it as follows:

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

Then when we call add as follows:

const result = add(1);

result is 3 since b is 2 and a is 1.

This is much better than writing:

const add = (a, b) => {
  b = b || 2;
  return a + b;
}

Since we don’t have to change the value of b and write 2 lines instead of 1.

ES6 Property Shorthands

Property shorthands are great because they let us add properties to our code JavaScript in a shorter way.

For instance, if we have:

const a = 1,
  b = 'foo',
  c = 3;

Instead of writing:

const obj = {
  a: a,
  b: b,
  c: c
}

We write:

const obj = {
  a,
  b,
  c
}

Both mean the same thing. It’s just that the shorthand is much shorter than the original version.

Now obj.a is 1, obj.b is 'foo' and obj.c is 3.

Singletons

A singleton is an object that can be the only instance of a class.

We can use that to manage anything global like app-wide state.

With ES6 or newer, we can just create a constant and then prevent the object from changing with Object.freeze .

For instance, we can write the following:

const data = [];
const Store = {
  add: item => data.push(item),
  getById: id => data.find(d => d.id === id)
}

Object.freeze(Store);

In the code above, we have the Store object with the add and getById methods.

The add method adds a new item to the end of the array, and getById finds the item by ID.

Then we call Object.freeze with the Store object to freeze the object in place.

We can also encapsulate data by putting it in a class as follows:

class Store {
  constructor() {
    this.data = [];
  }

  add(item) {
    this.data.push(item);
  }

  getById(id) {
    return this.data.find(d => d.id === id);
  }
}

const store = new Store();
Object.freeze(store);

In the code above, we have the Store class, which has the same methods as the Store object in the previous example.

Then we create a new store and then freeze it in place.

Now we have an immutable instance, but we can still create more than one instance of Store .

Therefore, we’ve to make that the constructor always returns the same instance.

To do that, we write:

class Store {
  constructor() {
    if (!Store.instance) {
      this.data = [];
      Store.instance = this;
    }
    return Store.instance;
  }

  add(item) {
    this.data.push(item);
  }

  getById(id) {
    return this.data.find(d => d.id === id);
  }
}

const store = new Store()
Object.freeze(store);

We have the following constructor:

constructor() {
  if (!Store.instance) {
    this.data = [];
    Store.instance = this;
  }
  return Store.instance;
}

We can return whatever we want when we instantiate a JavaScript class, so we can make sure that we always return the same instance by setting this to Store.instance .

Store.instance is static so it’s shared by all instances of the class.

Therefore, we can return it if it’s defined.

Conclusion

Default parameters are great for shortening our code so that we don’t have to set the parameter’s value with the || operator.

Instead, we just set the default value in the function signature.

The property shorthand is great because we don’t have to write out the colon all the time, we just list the parameter names and the values will be set if a variable or constant in the scope and has the same name as the property.

With the singleton pattern, we can always return the same instance of an object.

We just create an object and freeze it or we create a class that always returns the same instance of an object.

Categories
JavaScript Best Practices

JavaScript Clean Code — Name and Test Heuristics

Bad code has lots of unique characteristics. In this article, we’ll look at each one and what they are. We look at more general code smells.

In this article, we’ll look at how to name things properly, and to write a test that has high coverage and fast.

Naming

Use Long Names for Long Scopes

If the scope is long then the name should be longer than if they’re in a short scope.

Variables with short names lose their meaning over long distances.

Avoid Encodings

Names shouldn’t be encoded with scope information. We should avoid useless prefixes.

However, types in names can be useful in JavaScript since variables and constants have dynamic types.

Names Should Describe Side Effects

If a piece of code commits side effects, the name should reflect it. For example, if we have the following function:

let foo;  
const getFoo = () => {  
  if (!foo) {  
    foo = {}  
  }  
  return foo  
}

Then we should name it getFoo because we didn’t describe the obvious side effect in the name.

Instead, we should name it setFooIfNotExistAndGetFoo to take both actions into account.

Tests

Insufficient Tests

We should make sure that we have sufficient test coverage so that we know the code and branches work.

Also, our tests should take into account both positive and negative cases.

Boundary and edge cases should also have tests to test cases that may break our code.

Use a Coverage Tool

Test coverage tools tell us what we have tests for and what still needs test coverage.

They also tell us which branches of our code have test coverage.

Don’t Skip Trivial Tests

They’re easy to write and serves as good documentation.

An Ignored Test is a Question About an Ambiguity

An ignored test is always questionable. We don’t know why they’re skipped so we sure make the reason clear why we skip a test.

Test Boundary Conditions

Testing boundary conditions is important since code often breaks around boundary conditions.

Exhaustively Test Near Bugs

If a piece of code has bugs, then we should test those cases more.

Patterns of Failures are Revealing

Patterns that test cases fail can tell us a lot about what’s breaking our code.

Cases that fail tell usually have some pattern, like when they fail with 5 or more characters passed in or something like that.

Tests Should be Fast

Slow tests are torture to run. They just get ignored and won’t get run. We should make them as fast as possible, especially since there’s going to be lots of them.

Conclusion

We should name things in an unambiguous way. If there’re side effects, then we should describe them.

Also, tests should cover bugs and boundary cases and they should run fast.

We can use a coverage tool to get a clear idea of what pieces of code are covered by tests.

Categories
JavaScript Best Practices

JavaScript Clean Code — More Heuristics

Bad code has lots of unique characteristics. In this article, we’ll look at each one and what they are. We look at more general code smells.

Don’t Be Arbitrary

We should structure our code to follow clean code conventions. The code that doesn’t belong in the place shouldn’t be there.

If a team has a convention for the codebase, then everyone should stick to it. Otherwise, it gets messy fast.

Encapsulate Boundary Conditions

Boundary conditions should be put in a function or variable for easy access and understanding.

For example, we should set arr.length — 1 to a variable as follows if we have wanted it to be the end index variable of the loop:

const arr = [1, 2, 3];
const lastIndexOfArray = arr.length - 1;
for (let i = 0; i <= lastIndexOfArray; i++) {
  console.log(arr[i]);
}

We can see that once we assigned to arr.length — 1 to the constant lastIndexOfArray, then we know that it is the last index of the array.

We no longer have to think about what it means. From the constant name, we know what it means.

Similarly, we should do it for other boundary cases, so we don’t have to guess why we have +1 or -1 in different places.

Function Should Descend Only One Level of Abstraction

This means that functions should only do one thing. If we require it to do another thing at a different level of abstraction, then we should write a new function and call that.

Since functions should be small and should only do one thing, they shouldn’t be touching different things at different levels.

Keep Configurable Data at High Levels

Keeping configuration data at a high level keep them within our sight. Since we’re using them in many places, it makes sense for them to be at a high level.

If we put them at a high level, then they’re also easy to change.

Avoid Transitive Navigation

We should avoid code structure where we have A referencing B and B referencing C.

It’s harder on our brains to understand this kind of navigation. Also, it exposes us to more code that is coupled together.

We shouldn’t expose code that we don’t want to expose.

For example, something like:

foo.getBar().getBaz().doSomething();

The code above is bad because we have to get the Bar instance with getBar, and then with the Bar instance, we have to get the Baz instance with getBaz. Then we finally call doSomething on the Baz instance.

That’s fragile code, because if anything in the chain breaks, then the whole thing breaks.

Any of them changing will become an issue. We should take out this transitive navigation code by changing the implementation so that we don’t have this kind of structure.

Choose Descriptive Names

Naming things descriptively is important. Names tell us a lot about what a piece of code is doing. Therefore, we should choose names that tells what it’s storing or doing.

Names like x and y aren’t good names for variables since they tell us nothing about what they store.

On the other hand, numApples and numOranges are better names since they do tell us what they’re doing.

Choose Names at the Appropriate Level of Abstraction

We should think about the level of abstraction of our code when we name things with names.

For example, if we have the following Phone class:

class Phone {
  dial() {}
}

Then we are being too specific with our naming of the dial method when we actually want to convey that we’re using it to call another person.

Most phones don’t have dials anymore so it wouldn’t really make sense to name them as such. Some phones have keypads and smartphones have screens.

So instead, we should rename it to be more general as follows:

class Phone {
  call() {}
}

Unambiguous Names

Names shouldn’t be ambiguous. They shouldn’t have double meaning where people have to guess what the name actually means.

Guessing is bad since people may lead to the wrong conclusion and make mistakes when changing the code.

For example, if we want to create a function to rename a file, we shouldn’t call it rename since it doesn’t tell us what we’re renaming. Instead renameFile is more appropriate since we know for sure that the function is for renaming a file.

Conclusion

We should be clear when we’re naming things. Names should be descriptive and unambiguous.

Also, we should name things at the correct level of abstraction, so names that should be general should be generic, and the opposite is also true.

Transitive navigation is also bad because it creates fragile code. We shouldn’t have a chain of function calls that get different types of objects in order to do something.

Finally, we should encapsulate boundary conditions so we’re clear of what it is and why we have it.

Categories
JavaScript Best Practices

JavaScript Clean Code: Code and Coupling Heuristics

Bad code has lots of unique characters. In this article, we’ll look at each one and what they are. We look at more general code smells.


Too Much Information

Well-defined modules should be small and allow us to do a lot with little code. They don’t offer many functions to depend upon, so coupling is loose.

A bad module has lots of functions that we have to call to get something done, so coupling is high.

Things that are exposed to modules should be minimized. Data and implementation should be hidden except when they can’t be. We shouldn’t create lots of instance variables that are inherited by other classes.

Utility functions should be hidden from other modules. In the end, we should be able to reference a few things to do a lot to minimize coupling.


Dead Code

Dead code should definitely be removed from our codebase.

It’s ignored and it’s never updated to follow the latest conventions. Therefore, it should be removed from our code. Our tests should check if it works without the dead code.


Vertical Separation

Variables and functions should be closed to where they’re used so that we don’t have to do lots of scrolling to trace our code.


Inconsistency

We should be consistent with our naming. For example, we should stick to JavaScript conventions when naming things.

Functions and classes are upper camel case. Variables are camel case. Constants are upper case. Consistently named things are easier to modify.


Clutter

We should remove anything that clutters our code. Useless code should be removed. Unused entities should be gone.

Everything should be well-organized, clean, and free of clutter.


Artificial Coupling

Coupling should always be minimized so things that shouldn’t be coupled together stay uncoupled. The less one thing has to know about another, the better.

They serve no purpose and making changes is harder because we have to deal with all the coupling whenever we make changes. When we get something to work, we should clean up our code so that we don’t have these situations.


Feature Envy

The methods of a class should be interested in the variables and functions of the class they are in, and not the ones from other classes.

We should reference as little code from external sources as possible.

The following example shows what feature envy is:

We have the ShapeCalculator class that references the Rectangle class a lot. We call its constructor and instance variables.

However, we shouldn’t do this because it’s referencing too much from the Rectangle class. We can remove references to the instance variables as follows:

As we can see, we didn’t have to touch the internals to get the area of a rectangle. It’s much better to not reference the length and width from a Rectangle instance if we don’t have to.

This is because when we make changes to the Rectangle class, we have to change a lot of things in the ShapeCalculator class if we do reference these entities.

For example, when we change the names of length and width to something else, then we have to change them everywhere.


Selector Arguments

Boolean arguments for selecting functionality in a function is bad. It’s hard to know what true or false means when we select it. We should split a function into two if we do need a selector argument. It’s just as bad as any other argument.

Avoiding boolean arguments is one of the clear-cut criteria to separate a function into multiple functions. If we do need selectors, they should be something clearer like strings or integers.


Obscured Intent

We want our code to be as clear to our readers as possible. Therefore, naming should reveal the intent of identifiers.

So, variable x is a bad name since it tells us nothing, but numApples is good since we know what it means. We know that it stores a number of apples.


Misplaced Responsibility

Code should be placed where we expect it to be. For example, PI should belong to a Math class as a constant. We shouldn’t be clever about where to put certain functionalities. The place we put it should be intuitive to the reader.

Names of functions should tell us where to put our code. For instance, getHoursWorked should be in the Employee class because it belongs to an employee.


Inappropriate Static

Static methods should only be used on functions that don’t operate on an instance. So, getHoursWorked shouldn’t be a static method in the Employee class, since the number of hours worked belongs to the employee.

Something that’s suitable for static methods is the ones that don’t care about the instance of the class it operates on.

For example, the Math.min method should be a static method because we don’t need the Math instance for anything.


Conclusion

When writing clean code, we have to think about many things. However, most of them are common sense. We should write code that is clear and expose as little to the outside as possible to reduce coupling.

Names should be clear so everyone knows what we mean. Finally, things have to placed where they make sense.

Categories
JavaScript Best Practices

JavaScript Clean Code — Functions and Convention Heuristics

Bad code has lots of unique characters. In this article, we’ll look at each one and what they are. We look at more general code smells.

Use Explanatory Variables

Variables should have names that explain themselves. For example, something that isn’t explanatory is something like x or y. We don’t know what they mean.

On the other hand, numOranges or numApples are explanatory since they tell us what we’re going to store in these variables. We know that we’re setting it to the number of oranges and apples respectively.

Function Names Should Say What They Do

Function names need to tell what they’re doing so we don’t have to guess.

For example, we don’t know what date.add(2) does? It can be adding seconds, minutes, hours, days, months, or whatever we didn’t think of yet.

We should rename it to something more clear like addDays or addMonths so that we know what we’re adding.

If we have to look at the code or documentation to know what it does at a high level, then maybe it should be renamed.

Understand the Algorithm

We should understand the code that we write. Otherwise, we may hit a fluke once in a while, but if we don’t know what it’s doing exactly, then we’re going to run into problems eventually.

When we do run into problems, we won’t know how to resolve them since we didn’t understand what we wrote in the first place.

Also, writing code by guessing creates messy code as we’re messing with them to get them to work but we’re afraid that when we clean up the code, that it’ll fail again.

Therefore, we should think and understand before and during the time that we write our code.

Prefer Polymorphism to Conditionals

Conditionals are long messy. Nested ones are even worse. We should use them as little as possible if we’re just going to use them to invoke different objects.

Follow Standard Convention

Everyone should follow coding standards based on industry norms. In JavaScript, there’re conventions for naming variables, constants, and functions.

Also, the spacing and maximum line length are standardized across files.

We can deal with these automatically by using Linters and code formatters.

Other things like vertical formatting and function and variables placement have to be dealt with manually.

Replacing Magic Numbers with Named Constants

It’s hard to know what a number means when it’s not assigned to a constant.

Therefore, if we use a number as a constant, we should assign it to one so that we know what they mean.

For example, if we have a constant for hours per day, we should write:

const HOURS_PER_DAY = 24;

instead of just 24.

Other issues include floating-point numbers that need precision. To keep the precision the same, we should assign them to a constant.

Something like PI and E should be assigned to constants so that they always have the same precision.

In addition to numbers, they also apply to any other constant values that are repeatedly used. For example, if we always write tests using the string 'Joe', then we can assign it to a constant and reference it everywhere.

This way, we avoid errors typing and reduce the chance of creating bugs.

Be Precise

We should be precise with everything in our code. For example, we shouldn’t use the word array in our variable names unless it’s an array.

If we expect something to return null or undefined, then we should check for those.

Also, we should expect the first match of anything to be the correct match. We should actually check for the conditions that we’re looking for.

Structure over Convention

We should enforce structure over convention. We can shape the structure with tests and reviews.

Encapsulate Conditionals

When we have a conditional with multiple conditions, consider encapsulating them in a function.

For example, instead of writing:

if (employee.type === 'FULL_TIME' && employee.hasHealthBenefits) {

}

We can put the boolean expression into a function as follows:

const hasFullTimeHealthBenefits = (employee) => {
  return employee.type === 'FULL_TIME' && employee.hasHealthBenefits;
}

if (hasFullTimeHealthBenefits(employee)) {

}

Avoid Negative Conditionals

Negatives are hard on our brains, so we should use positive boolean expressions whenever we can. For example:

if (isEmployed) {

}

is better than:

if (!isNotEmployed) {

}

Functions Should Do One Thing

Functions should do only one thing. If a function does multiple things, then we should split it into smaller functions.

For example, if we have the following code:

const calculateTotalPay = (employees) => {
  let totalPay = 0;
  for (let employee of employees) {
    totalPay += employee.regularPay;
    totalPay += employee.overtimePay;
    totalPay += employee.bonus;
  }
  return totalPay;
}

We can instead move the totalPay calculations to its own function as follows:

const calculateEmployeePay = (employee) => {
  return employee.regularPay +
    employee.overtimePay +
    employee.bonus;
}

const calculateTotalPay = (employees) => {
  let totalPay = 0;
  for (let employee of employees) {
    totalPay += calculateEmployeePay(employee);
  }
  return totalPay;
}

Now we have one function to get the total pay and employee’s pay instead of one big function to get both the employee’s pay and the total pay of all employees.

Conclusion

We should follow standard conventions when writing code. Names should be clear, they should also follow the same case.

Double negatives are also hard to understand, so we should avoid them.

We should assign any literal values to constants if they’re used repeatedly.

Finally, functions should only do one thing to make them simple.