Clean Code Javascript
Clean Code Javascript
Clean Code Javascript
Table of Contents
1. Introduction
2. Variables
3. Functions
4. Objects and Data Structures
5. Classes
6. SOLID
7. Testing
8. Concurrency
9. Error Handling
10. Formatting
11. Comments
12. Translation
Introduction
1
One more thing: knowing these won’t immediately make you a better software
developer, and working with them for many years doesn’t mean you won’t make
mistakes. Every piece of code starts as a first draft, like wet clay getting shaped
into its final form. Finally, we chisel away the imperfections when we review it
with our peers. Don’t beat yourself up for first drafts that need improvement.
Beat up the code instead!
Variables
Use meaningful and pronounceable variable names
Bad:
const yyyymmdstr = moment().format("YYYY/MM/DD");
Good:
const currentDate = moment().format("YYYY/MM/DD");
� back to top
2
setTimeout(blastOff, MILLISECONDS_PER_DAY);
� back to top
3
dispatch(location);
});
� back to top
4
function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}
� back to top
Functions
Function arguments (2 or fewer ideally)
Limiting the amount of function parameters is incredibly important because it
makes testing your function easier. Having more than three leads to a combina-
torial explosion where you have to test tons of different cases with each separate
argument.
One or two arguments is the ideal case, and three should be avoided if possible.
Anything more than that should be consolidated. Usually, if you have more than
two arguments then your function is trying to do too much. In cases where it’s
not, most of the time a higher-level object will suffice as an argument.
Since JavaScript allows you to make objects on the fly, without a lot of class
boilerplate, you can use an object if you are finding yourself needing a lot of
arguments.
To make it obvious what properties the function expects, you can use the
ES2015/ES6 destructuring syntax. This has a few advantages:
1. When someone looks at the function signature, it’s immediately clear what
properties are being used.
2. It can be used to simulate named parameters.
3. Destructuring also clones the specified primitive values of the argument
object passed into the function. This can help prevent side effects. Note:
objects and arrays that are destructured from the argument object are
NOT cloned.
4. Linters can warn you about unused properties, which would be impossible
without destructuring.
Bad:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
5
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});
� back to top
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
� back to top
6
// It's hard to tell from the function name what is added
addToDate(date, 1);
Good:
function addMonthToDate(month, date) {
// ...
}
ast.forEach(node => {
// parse...
});
}
Good:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
7
const syntaxTree = parse(tokens);
syntaxTree.forEach(node => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
return tokens;
}
function parse(tokens) {
const syntaxTree = [];
tokens.forEach(token => {
syntaxTree.push(/* ... */ );
});
return syntaxTree;
}
� back to top
8
plicate code means creating an abstraction that can handle this set of different
things with just one function/module/class.
Getting the abstraction right is critical, that’s why you should follow the SOLID
principles laid out in the Classes section. Bad abstractions can be worse than
duplicate code, so be careful! Having said this, if you can make a good ab-
straction, do it! Don’t repeat yourself, otherwise you’ll find yourself updating
multiple places anytime you want to change one thing.
Bad:
function showDeveloperList(developers) {
developers.forEach(developer => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach(manager => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}
Good:
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
9
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}
� back to top
function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);
Good:
const menuConfig = {
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true
10
};
function createMenu(config) {
let finalConfig = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);
return finalConfig
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);
� back to top
function createTempFile(name) {
createFile(`./temp/${name}`);
}
� back to top
11
Avoid Side Effects (part 1)
A function produces a side effect if it does anything other than take a value
in and return another value or values. A side effect could be writing to a
file, modifying some global variable, or accidentally wiring all your money to a
stranger.
Now, you do need to have side effects in a program on occasion. Like the
previous example, you might need to write to a file. What you want to do is
to centralize where you are doing this. Don’t have several functions and classes
that write to a particular file. Have one service that does it. One and only one.
The main point is to avoid common pitfalls like sharing state between objects
without any structure, using mutable data types that can be written to by
anything, and not centralizing where your side effects occur. If you can do this,
you will be happier than the vast majority of other programmers.
Bad:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break i
let name = "Ryan McDermott";
function splitIntoFirstAndLastName() {
name = name.split(" ");
}
splitIntoFirstAndLastName();
12
function. A JavaScript function can change an object’s properties or alter the
contents of an array which could easily cause bugs elsewhere.
Suppose there’s a function that accepts an array parameter representing a shop-
ping cart. If the function makes a change in that shopping cart array - by adding
an item to purchase, for example - then any other function that uses that same
cart array will be affected by this addition. That may be great, however it
could also be bad. Let’s imagine a bad situation:
The user clicks the “Purchase” button which calls a purchase function that
spawns a network request and sends the cart array to the server. Because of a
bad network connection, the purchase function has to keep retrying the request.
Now, what if in the meantime the user accidentally clicks an “Add to Cart”
button on an item they don’t actually want before the network request begins?
If that happens and the network request begins, then that purchase function
will send the accidentally added item because the cart array was modified.
A great solution would be for the addItemToCart function to always clone the
cart, edit it, and return the clone. This would ensure that functions that are
still using the old shopping cart wouldn’t be affected by the changes.
Two caveats to mention to this approach:
1. There might be cases where you actually want to modify the input object,
but when you adopt this programming practice you will find that those
cases are pretty rare. Most things can be refactored to have no side effects!
2. Cloning big objects can be very expensive in terms of performance. Luck-
ily, this isn’t a big issue in practice because there are great libraries that
allow this kind of programming approach to be fast and not as memory
intensive as it would be for you to manually clone objects and arrays.
Bad:
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};
Good:
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};
� back to top
13
to extend JavaScript’s native Array method to have a diff method that could
show the difference between two arrays? You could write your new function
to the Array.prototype, but it could clash with another library that tried to
do the same thing. What if that other library was just using diff to find the
difference between the first and last elements of an array? This is why it would
be much better to just use ES2015/ES6 classes and simply extend the Array
global.
Bad:
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};
Good:
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}
� back to top
14
linesOfCode: 1000
}
];
let totalOutput = 0;
Encapsulate conditionals
Bad:
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}
Good:
function shouldShowSpinner(fsm, listNode) {
15
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
� back to top
if (!isDOMNodeNotPresent(node)) {
// ...
}
Good:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
� back to top
Avoid conditionals
This seems like an impossible task. Upon first hearing this, most people say,
“how am I supposed to do anything without an if statement?” The answer is
that you can use polymorphism to achieve the same task in many cases. The
second question is usually, “well that’s great but why would I want to do that?”
The answer is a previous clean code concept we learned: a function should only
do one thing. When you have classes and functions that have if statements, you
are telling your user that your function does more than one thing. Remember,
just do one thing.
Bad:
class Airplane {
// ...
getCruisingAltitude() {
switch (this.type) {
16
case "777":
return this.getMaxAltitude() - this.getPassengerCount();
case "Air Force One":
return this.getMaxAltitude();
case "Cessna":
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}
Good:
class Airplane {
// ...
}
17
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location("texas"));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location("texas"));
}
}
Good:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location("texas"));
}
� back to top
18
Don’t over-optimize
Modern browsers do a lot of optimization under-the-hood at runtime. A lot of
times, if you are optimizing then you are just wasting your time. There are
good resources for seeing where optimization is lacking. Target those in the
meantime, until they are fixed if they can be.
Bad:
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}
Good:
for (let i = 0; i < list.length; i++) {
// ...
}
� back to top
function newRequestModule(url) {
// ...
}
19
� back to top
return {
balance: 0
// ...
};
}
20
return {
// ...
getBalance,
setBalance
};
}
21
Classes
Prefer ES2015/ES6 classes over ES5 plain functions
It’s very difficult to get readable class inheritance, construction, and method
definitions for classical ES5 classes. If you need inheritance (and be aware
that you might not), then prefer ES2015/ES6 classes. However, prefer small
functions over classes until you find yourself needing larger and more complex
objects.
Bad:
const Animal = function(age) {
if (!(this instanceof Animal)) {
throw new Error("Instantiate Animal with `new`");
}
this.age = age;
};
Animal.call(this, age);
this.furColor = furColor;
};
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};
22
Good:
class Animal {
constructor(age) {
this.age = age;
}
move() {
/* ... */
}
}
liveBirth() {
/* ... */
}
}
speak() {
/* ... */
}
}
� back to top
23
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
}
setModel(model) {
this.model = model;
}
setColor(color) {
this.color = color;
}
save() {
console.log(this.make, this.model, this.color);
}
}
setMake(make) {
this.make = make;
// NOTE: Returning this for chaining
return this;
}
setModel(model) {
this.model = model;
// NOTE: Returning this for chaining
return this;
}
24
setColor(color) {
this.color = color;
// NOTE: Returning this for chaining
return this;
}
save() {
console.log(this.make, this.model, this.color);
// NOTE: Returning this for chaining
return this;
}
}
// ...
}
// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
25
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super();
this.ssn = ssn;
this.salary = salary;
}
// ...
}
Good:
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}
// ...
}
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}
� back to top
SOLID
Single Responsibility Principle (SRP)
As stated in Clean Code, “There should never be more than one reason for a
class to change”. It’s tempting to jam-pack a class with a lot of functionality,
like when you can only take one suitcase on your flight. The issue with this is
that your class won’t be conceptually cohesive and it will give it many reasons to
change. Minimizing the amount of times you need to change a class is important.
It’s important because if too much functionality is in one class and you modify a
piece of it, it can be difficult to understand how that will affect other dependent
modules in your codebase.
26
Bad:
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}
Good:
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
// ...
}
}
class UserSettings {
constructor(user) {
this.user = user;
this.auth = new UserAuth(user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...
}
}
}
� back to top
27
Open/Closed Principle (OCP)
As stated by Bertrand Meyer, “software entities (classes, modules, functions,
etc.) should be open for extension, but closed for modification.” What does
that mean though? This principle basically states that you should allow users
to add new functionalities without changing existing code.
Bad:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = "ajaxAdapter";
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
if (this.adapter.name === "ajaxAdapter") {
return makeAjaxCall(url).then(response => {
// transform response and return
});
} else if (this.adapter.name === "nodeAdapter") {
return makeHttpCall(url).then(response => {
// transform response and return
});
}
}
}
function makeAjaxCall(url) {
// request and return promise
}
function makeHttpCall(url) {
// request and return promise
28
}
Good:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = "ajaxAdapter";
}
request(url) {
// request and return promise
}
}
request(url) {
// request and return promise
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then(response => {
// transform response and return
});
}
}
� back to top
29
The best explanation for this is if you have a parent class and a child class,
then the base class and child class can be used interchangeably without getting
incorrect results. This might still be confusing, so let’s take a look at the
classic Square-Rectangle example. Mathematically, a square is a rectangle, but
if you model it using the “is-a” relationship via inheritance, you quickly get into
trouble.
Bad:
class Rectangle {
constructor() {
this.width = 0;
this.height = 0;
}
setColor(color) {
// ...
}
render(area) {
// ...
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
setHeight(height) {
this.width = height;
this.height = height;
}
}
30
function renderLargeRectangles(rectangles) {
rectangles.forEach(rectangle => {
rectangle.setWidth(4);
rectangle.setHeight(5);
const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
rectangle.render(area);
});
}
render(area) {
// ...
}
}
getArea() {
return this.width * this.height;
}
}
getArea() {
return this.length * this.length;
}
}
31
function renderLargeShapes(shapes) {
shapes.forEach(shape => {
const area = shape.getArea();
shape.render(area);
});
}
const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);
� back to top
setup() {
this.rootNode = this.settings.rootNode;
this.settings.animationModule.setup();
}
traverse() {
// ...
}
}
32
animationModule() {} // Most of the time, we won't need to animate when traversing.
// ...
});
Good:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.options = settings.options;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.setupOptions();
}
setupOptions() {
if (this.options.animationModule) {
// ...
}
}
traverse() {
// ...
}
}
33
you’ve seen an implementation of this principle in the form of Dependency Injec-
tion (DI). While they are not identical concepts, DIP keeps high-level modules
from knowing the details of its low-level modules and setting them up. It can
accomplish this through DI. A huge benefit of this is that it reduces the coupling
between modules. Coupling is a very bad development pattern because it makes
your code hard to refactor.
As stated previously, JavaScript doesn’t have interfaces so the abstractions that
are depended upon are implicit contracts. That is to say, the methods and prop-
erties that an object/class exposes to another object/class. In the example below,
the implicit contract is that any Request module for an InventoryTracker will
have a requestItems method.
Bad:
class InventoryRequester {
constructor() {
this.REQ_METHODS = ["HTTP"];
}
requestItem(item) {
// ...
}
}
class InventoryTracker {
constructor(items) {
this.items = items;
requestItems() {
this.items.forEach(item => {
this.requester.requestItem(item);
});
}
}
34
this.items = items;
this.requester = requester;
}
requestItems() {
this.items.forEach(item => {
this.requester.requestItem(item);
});
}
}
class InventoryRequesterV1 {
constructor() {
this.REQ_METHODS = ["HTTP"];
}
requestItem(item) {
// ...
}
}
class InventoryRequesterV2 {
constructor() {
this.REQ_METHODS = ["WS"];
}
requestItem(item) {
// ...
}
}
Testing
Testing is more important than shipping. If you have no tests or an inadequate
amount, then every time you ship code you won’t be sure that you didn’t break
anything. Deciding on what constitutes an adequate amount is up to your team,
35
but having 100% coverage (all statements and branches) is how you achieve very
high confidence and developer peace of mind. This means that in addition to
having a great testing framework, you also need to use a good coverage tool.
There’s no excuse to not write tests. There are plenty of good JS test frame-
works, so find one that your team prefers. When you find one that works for
your team, then aim to always write tests for every new feature/module you
introduce. If your preferred method is Test Driven Development (TDD), that
is great, but the main point is to just make sure you are reaching your coverage
goals before launching any feature, or refactoring an existing one.
describe("MomentJS", () => {
it("handles date boundaries", () => {
let date;
describe("MomentJS", () => {
it("handles 30-day months", () => {
const date = new MomentJS("1/1/2015");
date.addDays(30);
assert.equal("1/31/2015", date);
});
36
date.addDays(28);
assert.equal("02/29/2016", date);
});
Concurrency
Use Promises, not callbacks
Callbacks aren’t clean, and they cause excessive amounts of nesting. With
ES2015/ES6, Promises are a built-in global type. Use them!
Bad:
import { get } from "request";
import { writeFile } from "fs";
get(
"https://en.wikipedia.org/wiki/Robert_Cecil_Martin",
(requestErr, response, body) => {
if (requestErr) {
console.error(requestErr);
} else {
writeFile("article.html", body, writeErr => {
if (writeErr) {
console.error(writeErr);
} else {
console.log("File written");
}
});
}
}
);
Good:
import { get } from "request-promise";
import { writeFile } from "fs-extra";
get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
.then(body => {
37
return writeFile("article.html", body);
})
.then(() => {
console.log("File written");
})
.catch(err => {
console.error(err);
});
� back to top
get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
.then(body => {
return writeFile("article.html", body);
})
.then(() => {
console.log("File written");
})
.catch(err => {
console.error(err);
});
Good:
import { get } from "request-promise";
import { writeFile } from "fs-extra";
38
console.error(err);
}
}
getCleanCodeArticle()
� back to top
Error Handling
Thrown errors are a good thing! They mean the runtime has successfully iden-
tified when something in your program has gone wrong and it’s letting you
know by stopping function execution on the current stack, killing the process
(in Node), and notifying you in the console with a stack trace.
39
Bad:
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
});
Good:
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
});
� back to top
Formatting
Formatting is subjective. Like many rules herein, there is no hard and fast rule
that you must follow. The main point is DO NOT ARGUE over formatting.
There are tons of tools to automate this. Use one! It’s a waste of time and
money for engineers to argue over formatting.
For things that don’t fall under the purview of automatic formatting (indenta-
tion, tabs vs. spaces, double vs. single quotes, etc.) look here for some guidance.
40
const songs = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const Artists = ["ACDC", "Led Zeppelin", "The Beatles"];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}
Good:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}
� back to top
lookupPeers() {
return db.lookup(this.employee, "peers");
}
lookupManager() {
return db.lookup(this.employee, "manager");
}
getPeerReviews() {
41
const peers = this.lookupPeers();
// ...
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getManagerReview() {
const manager = this.lookupManager();
}
getSelfReview() {
// ...
}
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, "peers");
}
getManagerReview() {
const manager = this.lookupManager();
}
42
lookupManager() {
return db.lookup(this.employee, "manager");
}
getSelfReview() {
// ...
}
}
Comments
Only comment things that have business logic complexity.
Comments are an apology, not a requirement. Good code mostly documents
itself.
Bad:
function hashIt(data) {
// The hash
let hash = 0;
// Length of string
const length = data.length;
43
const char = data.charCodeAt(i);
hash = (hash << 5) - hash + char;
44
Avoid positional markers
They usually just add noise. Let the functions and variable names along with
the proper indentation and formatting give the visual structure to your code.
Bad:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
menu: "foo",
nav: "bar"
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
// ...
};
Good:
$scope.model = {
menu: "foo",
nav: "bar"
};
Translation
This is also available in other languages:
• Armenian: hanumanum/clean-code-javascript/
• Bangla(�����): InsomniacSabbir/clean-code-javascript/
• Simplified Chinese:
– alivebao/clean-code-js
45
– beginor/clean-code-javascript
• French: eugene-augier/clean-code-javascript-fr
• German: marcbruederlin/clean-code-javascript
• Indonesia: andirkh/clean-code-javascript/
• Italian: frappacchio/clean-code-javascript/
• Japanese: mitsuruog/clean-code-javascript/
• Korean: qkraudghgh/clean-code-javascript-ko
• Polish: greg-dev/clean-code-javascript-pl
• Russian:
– BoryaMogila/clean-code-javascript-ru/
– maksugr/clean-code-javascript
• Spanish: tureey/clean-code-javascript
• Spanish: andersontr15/clean-code-javascript
• Serbian: doskovicmilos/clean-code-javascript/
• Turkish: bsonmez/clean-code-javascript
• Ukrainian: mindfr1k/clean-code-javascript-ua
• Vietnamese: hienvd/clean-code-javascript/
� back to top
46