
Summary by: Ahmad Wali Sharify
This is not a style guide. It’s a guide to producing readable, reusable, and refactorable software in JavaScript.
Don’t beat yourself up for first drafts that need improvement. Beat up the code instead!
// Meaningfull names
const currentDate = moment().format('YYYY/MM/DD')
// Same vocabulary
getUser(); // not getUserInfo(), getClientData()
// Searchable names
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; // 86400000
// Avoid Mental Mapping
locations.forEach(location => { // not l, for location
dispatch(location);
});
// Don’t add unneeded context
const Car = {
carMake: 'Honda', // correct: make
carModel: 'Accord', // correct: model
}
// default parameters instead of short circuiting or conditionals
function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}
Object.assign// function arguments (2 or fewer) or destructuring it
function createMenu ({title, body, buttoonText, cancellable}) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText:'Baz',
cancellable: true
});
// Functions should do one thing (important)
function emailActiveClients (clients) {
clients.filter(isActiveClient).forEach(email)
};
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
// Function Names should say what they do
function addMonthToDate (month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);
// avoid duplicate code, abstraction right is critical, use SOLID
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}
// Set default objects with Object.assign
const menuConfig = {
title: "Order",
buttonText: "Send",
cancellable: true
};
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);
// Don’t use flags as function parameters
function createFile(name) { // aginst createFile(name, temp) for flag for temp
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}
// Avoid Side Effect 1
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};
// Avoid Side Effects 2
function splitIntoFirstAndLastName(name) {
return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
// Encapsulate conditionals
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
// Use Getters and Setters
function makeBankAccount() {
// this one is private
let balance = 0;
// a "getter", made public via the returned object below
function getBalance() {
return balance;
}
// a "setter", made public via the returned object below
function setBalance(amount) {
// ... validate before updating the balance
balance = amount;
}
return {
getBalance,
setBalance
};
}
const account = makeBankAccount();
account.setBalance(100)
// Make Objects have private memebers
function makeEmployee(name) {
return {
getName() {
return name;
}
};
}
const employee = makeEmployee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
// Prefer ES2015/ES6 classes over ES5 plain functions
class Animal {
constructor(age) {
this.age = age;
}
move() {
}
}
class Mammal extends Animal {
constructor(age, furColor) {
super(age);
this.furColor = furColor;
}
liveBirth() {
}
}
class Human extends Mammal {
constructor(age, furColor, languageSpoken) {
super(age, furColor);
this.languageSpoken = languageSpoken;
}
speak() {
}
}
// Use Method chaining
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = 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;
}
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;
}
}
const car = new Car("Ford", "F-150", "red").setColor("pink").save() // important part
// Prefer compostion
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);
}
}
// SRP
function calculateSalary(employee) {
return employee.hoursWorked * employee.hourlyRate;
}
function generateReport(employee, salary) {
let report = /*...*/;
console.log(report);
}
// OCP, Open to extend, close to modify
function processPaymentWithPayPal(price, accountDetails) {
/*...*/
console.log('Payed with PayPal.');
}
// LSP
function makeRequest(url, errorHandler) {
fetch(url)
.then(response => response.json())
.catch(error => errorHandler.handle(error))
}
// We can have several functions to handle errors
const consoleErrorHandler = function handle(error){
console.log(error)
}
const externalErrorHandler = function handle(error){
sendErrorToExternalService(error)
}
makeRequest(url, consoleErrorHandler);
makeRequest(url, externalErrorHandler);
// ISP, no class should be forced to implement interfaces or methods that it will not // use.
class Product {
constructor() { /* */ }
getDetails() { /* */ }
saveToDb() {/* */ }
displayInFrontEnd() { /* */ }
}
class DigitalProduct extends Product{
// saveToDb() is extended but not necessary }
}
// Refactor
class Product {
constructor() { /* */ }
getDetails() { /* */ }
displayInFrontEnd() { /* */ }
}
class PhysicalProduct extends Product {
constructor() {
super()
}
saveToDb() { /* */ }
}
class DigitalProduct extends Product{
// saveToDb() is not extended
}
// DIP, High level functionality and classes should not depend on low-level function /n //(MySQLConnetion)
class MySqlConnection {
connect() { /* */ }
}
class PasswordReminder {
constructor() {
this.dbConnection = new MySQLConnection();
}
}
// Refactor
class MySqlConnection {
connect() { /* */ }
}
class PostgreSqlConnection {
connect() { /* */ }
}
class PasswordReminder {
constructor(connection) {
this.dbConnection = connection
}
}
import assert from "assert";
describe("MomentJS", () => {
it("handles 30-day months", () => {
const date = new MomentJS("1/1/2015");
date.addDays(30);
assert.equal("1/31/2015", date);
});
it("handles leap year", () => {
const date = new MomentJS("2/1/2016");
date.addDays(28);
assert.equal("02/29/2016", date);
});
it("handles non-leap year", () => {
const date = new MomentJS("2/1/2015");
date.addDays(28);
assert.equal("03/01/2015", date);
});
});
import { get } from "request-promise";
import { writeFile } from "fs-extra";
async function getCleanCodeArticle() {
try {
const body = await get(
"https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
);
await writeFile("article.html", body);
console.log("File written");
} catch (err) {
console.error(err);
}
}
getCleanCodeArticle()
Thrown errors are a good thing!
try {
functionThatMightThrow();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
}
DO NOT ARGUE over formatting
Sign in to join the conversation.
Sign in