The only way to test code quality is the number of times someone says “F * K” when they look at your code.
Code quality is proportional to its cleanliness. Clean code, both in quality is more reliable, but also for the later maintenance, upgrade laid a good foundation.
This article is not a code style guide, but a discussion of code readability, reusability, and extensibility.
We will discuss it from several aspects:
- variable
- function
- Objects and data structures
- class
- SOLID
- test
- asynchronous
- Error handling
- Code style.
- annotation
variable
Name variables with meaningful and commonly used words
Bad:
const yyyymmdstr = moment().format('YYYY/MM/DD');
Copy the code
Good:
const currentDate = moment().format('YYYY/MM/DD');
Copy the code
Maintain unity
The same project may have three different names for retrieving user information. It should be consistent. If you don’t know what to call it, go to Codelf and find out what other people call it.
Bad:
getUserInfo();
getClientData();
getCustomerRecord();
Copy the code
Good:
getUser()
Copy the code
↑ Back to top
Each constant should be named
You can use buddy. Js or ESLint to detect unnamed constants in code.
Bad:
// Will you still know what 86400000 is in three months?
setTimeout(blastOff, 86400000);
Copy the code
Good:
const MILLISECOND_IN_A_DAY = 86400000;
setTimeout(blastOff, MILLISECOND_IN_A_DAY);
Copy the code
↑ Back to top
Can be described
A new variable is created from a variable, and the new variable needs to be named, which means that you know what each variable does when you first see it.
Bad:
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?) \s*(\d{5})? $/;
saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1], ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);
Copy the code
Good:
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?) \s*(\d{5})? $/;
const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || [];
saveCityZipCode(city, zipCode);
Copy the code
↑ Back to top
straightforward
Bad:
const locations = ['Austin'.'New York'.'San Francisco'];
locations.forEach((l) = > {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// We need to look at the other code to determine what 'l' is.
dispatch(l);
});
Copy the code
Good:
const locations = ['Austin'.'New York'.'San Francisco'];
locations.forEach((location) = > {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});
Copy the code
↑ Back to top
Avoid meaningless prefixes
If you create an object car, there is no need to name its color carColor.
Bad:
const car = {
carMake: 'Honda'.carModel: 'Accord'.carColor: 'Blue'
};
function paintCar(car) {
car.carColor = 'Red';
}
Copy the code
Good:
const car = {
make: 'Honda'.model: 'Accord'.color: 'Blue'
};
function paintCar(car) {
car.color = 'Red';
}
Copy the code
↑ Back to top
Use default values
Bad:
function createMicrobrewery(name) {
const breweryName = name || 'Hipster Brew Co.';
// ...
}
Copy the code
Good:
function createMicrobrewery(name = 'Hipster Brew Co.') {
// ...
}
Copy the code
↑ Back to top
function
The fewer parameters, the better
If there are more than two arguments, use the ES2015/ES6 deconstruction syntax, regardless of the order of the arguments.
Bad:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
Copy the code
Good:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: 'Foo'.body: 'Bar'.buttonText: 'Baz'.cancellable: true
});
Copy the code
Just do one thing
This is an old rule in software engineering. Following this rule will make your code more readable and easier to refactor. If you break this rule, your code will be difficult to test or reuse.
Bad:
function emailClients(clients) {
clients.forEach((client) = > {
const clientRecord = database.lookup(client);
if(clientRecord.isActive()) { email(client); }}); }Copy the code
Good:
function emailActiveClients(clients) {
clients
.filter(isActiveClient)
.forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
Copy the code
↑ Back to top
As the name implies
The name of the function should tell you what it does.
Bad:
function addToDate(date, month) {
// ...
}
const date = new Date(a);// It's hard to know what to add to the date
addToDate(date, 1);
Copy the code
Good:
function addMonthToDate(month, date) {
// ...
}
const date = new Date(a); addMonthToDate(1, date);
Copy the code
↑ Back to top
Only one layer of abstraction is required
If the function is nested too much, it will be difficult to reuse and test.
Bad:
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) = > {
statements.forEach((statement) = > {
// ...
});
});
const ast = [];
tokens.forEach((token) = > {
// lex...
});
ast.forEach((node) = > {
// parse...
});
}
Copy the code
Good:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const ast = lexer(tokens);
ast.forEach((node) = > {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) = > {
statements.forEach((statement) = > {
tokens.push( / *... * / );
});
});
return tokens;
}
function lexer(tokens) {
const ast = [];
tokens.forEach((token) = > {
ast.push( / *... * / );
});
return ast;
}
Copy the code
↑ Back to top
Delete duplicate code
A lot of times it’s the same function, but because of one or two differences, you have to write two almost identical functions.
Optimizing repetitive code requires strong abstraction, and bad abstraction is worse than repetitive code. Therefore, the principle of SOLID must be followed in the abstraction process (what is SOLID? More on that later).
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);
});
}
Copy the code
Good:
function showEmployeeList(employees) {
employees.forEach(employee= > {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience,
};
switch(employee.type) {
case 'develop':
data.githubLink = employee.getGithubLink();
break
case 'manager':
data.portfolio = employee.getMBAProjects();
break} render(data); })}Copy the code
↑ Back to top
Object sets default properties
Bad:
const menuConfig = {
title: null.body: 'Bar'.buttonText: null.cancellable: true
};
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);
Copy the code
Good:
const menuConfig = {
title: 'Order'.// The 'body' key is missing
buttonText: 'Send'.cancellable: true
};
function createMenu(config) {
config = Object.assign({
title: 'Foo'.body: 'Bar'.buttonText: 'Baz'.cancellable: true
}, config);
{title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);
Copy the code
↑ Back to top
Do not pass the flag parameter
Judging execution logic by the true or false of flag violates the principle of one function doing one thing.
Bad:
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else{ fs.create(name); }}Copy the code
Good:
function createFile(name) {
fs.create(name);
}
function createFileTemplate(name) {
createFile(`./temp/${name}`)}Copy the code
↑ Back to top
Avoid Side effects (Part 1)
Functions take in a value and return a new value. Other actions we call side effects, such as modifying global variables, doing IO operations on files, etc.
When a function really needs side effects, such as IO operations on files, do not use multiple functions/classes for file operations. Use only one function/class for file operations. Which means side effects need to be dealt with in one place.
The three main sinkholes of side effects: haphazard modification of mutable data types, haphazard sharing of state without data structure, and failure to deal with side effects in a unified place.
Bad:
// The global variable is referenced by a function
// Now that the variable has changed from a string to an array, unexpected errors can occur if there are other function references.
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];
Copy the code
Good:
var name = 'Ryan McDermott';
var newName = splitIntoFirstAndLastName(name)
function splitIntoFirstAndLastName(name) {
return name.split(' ');
}
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
Copy the code
↑ Back to top
Avoid Side effects (Part 2)
In JavaScript, primitive types are passed by assignment and objects and arrays are passed by reference. Take reference passing as an example:
Suppose we write a shopping cart and add items to the cart using the addItemToCart() method, modifying the cart array. The purchase() method is called at this point, and because of the passing of the reference, the shopping cart array is exactly the latest data.
Looks good, doesn’t it?
If the network fails when the user clicks to buy, the purchase() method is repeatedly called while the user adds new items, and the network is restored. Then the purchase() method gets the shopping cart array incorrectly.
To avoid this problem, we need to clone the shopping cart array and return the new array each time we add an item.
Bad:
const addItemToCart = (cart, item) = > {
cart.push({ item, date: Date.now() });
};
Copy the code
Good:
const addItemToCart = (cart, item) = > {
return [...cart, {item, date: Date.now()}]
};
Copy the code
↑ Back to top
Do not write global methods
In JavaScript, you should never pollute globally, creating unexpected bugs in a production environment. For example, if you add a diff method to array. prototype to determine the difference between two arrays. Your colleague is trying to do something similar, but his diff method is used to determine the difference between the first elements of two arrays. Obviously your methods will clash, so we can use ES2015/ES6 syntax to extend Array.
Bad:
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem= >! hash.has(elem)); };Copy the code
Good:
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem= > !hash.has(elem));
}
}
Copy the code
↑ Back to top
I prefer functional programming to imperative
Functional change programming can make the logic of the code clearer and more elegant, easy to test.
Bad:
const programmerOutput = [
{
name: 'Uncle Bobby'.linesOfCode: 500
}, {
name: 'Suzie Q'.linesOfCode: 1500
}, {
name: 'Jimmy Gosling'.linesOfCode: 150
}, {
name: 'Gracie Hopper'.linesOfCode: 1000}];let totalOutput = 0;
for (let i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}
Copy the code
Good:
const programmerOutput = [
{
name: 'Uncle Bobby'.linesOfCode: 500
}, {
name: 'Suzie Q'.linesOfCode: 1500
}, {
name: 'Jimmy Gosling'.linesOfCode: 150
}, {
name: 'Gracie Hopper'.linesOfCode: 1000}];let totalOutput = programmerOutput
.map(output= > output.linesOfCode)
.reduce((totalLines, lines) = > totalLines + lines, 0)
Copy the code
↑ Back to top
Encapsulating conditional statement
Bad:
if (fsm.state === 'fetching' && isEmpty(listNode)) {
// ...
}
Copy the code
Good:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
Copy the code
↑ Back to top
Try not to use “not” conditionals
Bad:
function isDOMNodeNotPresent(node) {
// ...
}
if(! isDOMNodeNotPresent(node)) {// ...
}
Copy the code
Good:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
Copy the code
↑ Back to top
Avoid conditional statements
Q: It is impossible to write code without conditional statements.
A: Most scenarios can be replaced by polymorphism.
Q: Polymorphisms work, but why not conditional statements?
A: To make your code more concise and readable, if you have A conditional judgment in your function, then your function is doing more than one thing, violating the single function rule.
Bad:
class Airplane {
// ...
// Obtain cruising altitude
getCruisingAltitude() {
switch (this.type) {
case '777':
return this.getMaxAltitude() - this.getPassengerCount();
case 'Air Force One':
return this.getMaxAltitude();
case 'Cessna':
return this.getMaxAltitude() - this.getFuelExpenditure(); }}}Copy the code
Good:
class Airplane {
// ...
}
/ / a Boeing 777
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount(); }}// Air Force One
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude(); }}// Senas aircraft
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure(); }}Copy the code
↑ Back to top
Avoiding type checking (Part 1)
JavaScript is untyped, which means you can pass any type of argument you want, and this freedom can get confusing and cause you to automatically check the type. Do you really need to check the type or is there something wrong with your API design?
Bad:
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')); }}Copy the code
Good:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}
Copy the code
↑ Back to top
Avoiding Type checking (Part 2)
If you need to do static type checking, such as strings, integers, etc., use TypeScript; otherwise your code will get long and smelly.
Bad:
function combine(val1, val2) {
if (typeof val1 === 'number' && typeof val2 === 'number' ||
typeof val1 === 'string' && typeof val2 === 'string') {
return val1 + val2;
}
throw new Error('Must be of type String or Number');
}
Copy the code
Good:
function combine(val1, val2) {
return val1 + val2;
}
Copy the code
↑ Back to top
Don’t over-optimize
Modern browsers have done a lot of low-level optimizations, and many of the optimizations in the past were ineffective and wasted your time. To see what modern browsers have optimized, click here.
Bad:
// In older browsers, since 'list.length' is not cached, each iteration is computed, causing unnecessary overhead.
Modern browsers have been optimized for this.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}
Copy the code
Good:
for (let i = 0; i < list.length; i++) {
// ...
}
Copy the code
↑ Back to top
Remove deprecated code
Most of the time some code has no use, but worry about the future will use, reluctant to delete.
If you forget about it, the code will be there forever.
Delete it. You can find it in the repository history.
Bad:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Copy the code
Good:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Copy the code
↑ Back to top
Objects and data structures
withget
,set
Method operation data
This has many benefits, such as logging data to track errors; It’s easy to verify data when setting…
Bad:
function makeBankAccount() {
// ...
return {
balance: 0.// ...
};
}
const account = makeBankAccount();
account.balance = 100;
Copy the code
Good:
function makeBankAccount() {
// Private variables
let balance = 0;
function getBalance() {
return balance;
}
function setBalance(amount) {
/ /... Verify amount before updating balance
balance = amount;
}
return {
// ...
getBalance,
setBalance,
};
}
const account = makeBankAccount();
account.setBalance(100);
Copy the code
Use private variables
Closures can be used to create private variables
Bad:
const Employee = function(name) {
this.name = name;
};
Employee.prototype.getName = function getName() {
return this.name;
};
const employee = new Employee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined
Copy the code
Good:
function makeEmployee(name) {
return {
getName() {
returnname; }}; }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
Copy the code
↑ Back to top
class
Use the class
Before ES2015/ES6, there was no syntax for classes, and only constructors were used to simulate classes, which were very unreadable.
Bad:
/ / animals
const Animal = function(age) {
if(! (this instanceof Animal)) {
throw new Error('Instantiate Animal with `new`');
}
this.age = age;
};
Animal.prototype.move = function move() {};
// Mammals
const Mammal = function(age, furColor) {
if(! (this instanceof Mammal)) {
throw new Error('Instantiate Mammal with `new`');
}
Animal.call(this, age);
this.furColor = furColor;
};
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};
/ / human
const Human = function(age, furColor, languageSpoken) {
if(! (this instanceof Human)) {
throw new Error('Instantiate Human with `new`');
}
Mammal.call(this, age, furColor);
this.languageSpoken = languageSpoken;
};
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};
Copy the code
Good:
/ / animals
class Animal {
constructor(age) {
this.age = age
};
move() {};
}
// Mammals
class Mammal extends Animal{
constructor(age, furColor) {
super(age);
this.furColor = furColor;
};
liveBirth() {};
}
/ / human
class Human extends Mammal{
constructor(age, furColor, languageSpoken) {
super(age, furColor);
this.languageSpoken = languageSpoken;
};
speak() {};
}
Copy the code
Chain calls
This pattern is quite useful and can be found in many libraries, such as jQuery, Lodash, etc. It keeps your code simple and elegant. It’s easy to implement, returning this at the end of a class method.
Bad:
class Car {
constructor(make, model, color) {
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); }}const car = new Car('Ford'.'F-150'.'red');
car.setColor('pink');
car.save();
Copy the code
Good:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
return this;
}
setModel(model) {
this.model = model;
return this;
}
setColor(color) {
this.color = color;
return this;
}
save() {
console.log(this.make, this.model, this.color);
return this; }}const car = new Car('Ford'.'F-150'.'red')
.setColor('pink');
.save();
Copy the code
↑ Back to top
Don’t abuse inheritance
A lot of times inheritance is abused, resulting in poor readability. To figure out the relationship between two classes, inheritance represents a relationship of ownership, not of inclusion, e.g. Human->Animal vs. User->UserDetails
Bad:
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
// ...
}
// TaxData is not an Employee, but a containment relationship.
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super(a);this.ssn = ssn;
this.salary = salary;
}
// ...
}
Copy the code
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);
}
// ...
}
Copy the code
↑ Back to top
SOLID
SOLID is the combination of several words representing the single-function principle, the open and close principle, the Richter substitution principle, the interface isolation principle, and the dependency inversion principle.
Single function principle
If a class does too many things, it will be difficult to maintain later. We should clarify our responsibilities and reduce our dependence on each other.
Bad:
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...}}Copy the code
Good:
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
// ...}}class UserSetting {
constructor(user) {
this.user = user;
this.auth = new UserAuth(this.user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...}}}}Copy the code
The open closed principle
“Open” means that classes, modules, and functions should be extensible, and “closed” means that they should not be modified. This means that you can add features but not modify the source code.
Bad:
class AjaxAdapter extends Adapter {
constructor() {
super(a);this.name = 'ajaxAdapter'; }}class NodeAdapter extends Adapter {
constructor() {
super(a);this.name = 'nodeAdapter'; }}class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
if (this.adapter.name === 'ajaxAdapter') {
return makeAjaxCall(url).then((response) = > {
// Pass response and return
});
} else if (this.adapter.name === 'httpNodeAdapter') {
return makeHttpCall(url).then((response) = > {
// Pass response and return}); }}}function makeAjaxCall(url) {
// Process request and return promise
}
function makeHttpCall(url) {
// Process request and return promise
}
Copy the code
Good:
class AjaxAdapter extends Adapter {
constructor() {
super(a);this.name = 'ajaxAdapter';
}
request(url) {
// Process request and return promise}}class NodeAdapter extends Adapter {
constructor() {
super(a);this.name = 'nodeAdapter';
}
request(url) {
// Process request and return promise}}class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then((response) = > {
// Pass response and return}); }}Copy the code
↑ Back to top
Richter’s substitution principle
The name is spooky, but the truth is that subclasses should not override the methods of their parent class.
Bad:
/ / rectangle
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; }}/ / square
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width;
}
setHeight(height) {
this.width = height;
this.height = height; }}function renderLargeRectangles(rectangles) {
rectangles.forEach((rectangle) = > {
rectangle.setWidth(4);
rectangle.setHeight(5);
const area = rectangle.getArea();
rectangle.render(area);
});
}
const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);
Copy the code
Good:
class Shape {
setColor(color) {
// ...
}
render(area) {
// ...}}class Rectangle extends Shape {
constructor(width, height) {
super(a);this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height; }}class Square extends Shape {
constructor(length) {
super(a);this.length = length;
}
getArea() {
return this.length * this.length; }}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);
Copy the code
↑ Back to top
Interface Isolation Principle
JavaScript has almost no concept of interfaces, so this principle is rarely used. The official definition is that “a client should not rely on interfaces it does not need”, which is to minimize interfaces and decouple them.
Bad:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.animationModule.setup();
}
traverse() {
// ...}}const$=new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
animationModule() {} // Most of the time, we won't need to animate when traversing.
// ...
});
Copy the code
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() {
// ...}}const$=new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
options: {
animationModule() {}
}
});
Copy the code
↑ Back to top
Dependency inversion principle
Just two points:
- High-level modules cannot depend on low-level modules, which depend on abstract interfaces.
- Abstract interfaces cannot depend on concrete implementations, which depend on abstract interfaces.
It boils down to two words: decoupling.
Bad:
// Inventory query
class InventoryRequester {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...}}// Inventory tracking
class InventoryTracker {
constructor(items) {
this.items = items;
// We rely on a special request class, but we just need a request method.
this.requester = new InventoryRequester();
}
requestItems() {
this.items.forEach((item) = > {
this.requester.requestItem(item); }); }}const inventoryTracker = new InventoryTracker(['apples'.'bananas']);
inventoryTracker.requestItems();
Copy the code
Good:
// Inventory tracking
class InventoryTracker {
constructor(items, requester) {
this.items = items;
this.requester = requester;
}
requestItems() {
this.items.forEach((item) = > {
this.requester.requestItem(item); }); }}/ / HTTP requests
class InventoryRequesterHTTP {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...}}/ / the webSocket request
class InventoryRequesterWS {
constructor() {
this.REQ_METHODS = ['WS'];
}
requestItem(item) {
// ...}}// Decouple the request module through dependency injection so that we can easily replace it with webSocket requests.
const inventoryTracker = new InventoryTracker(['apples'.'bananas'].new InventoryRequesterHTTP());
inventoryTracker.requestItems();
Copy the code
↑ Back to top
test
As the project gets bigger and the timeline gets longer, some old code may not be touched for half a year. If you go live at this point, are you confident that it will work? Test coverage is proportional to your confidence.
PS: If you find your code difficult to test, then you should optimize your code.
Simplifying assumptions
Bad:
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles date boundaries', () = > {let date;
date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});
Copy the code
Good:
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles 30-day months', () = > {const date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
});
it('handles leap year', () = > {const date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
});
it('handles non-leap year', () = > {const date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});
Copy the code
asynchronous
No more callbacks
No one wants to look at code with nested callbacks. Promise callbacks instead.
Bad:
import { get } from 'request';
import { writeFile } from 'fs';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {
if (requestErr) {
console.error(requestErr);
} else {
writeFile('article.html', response.body, (writeErr) => {
if (writeErr) {
console.error(writeErr);
} else {
console.log('File written'); }}); }});Copy the code
Good:
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) = > {
return writeFile('article.html', response);
})
.then((a)= > {
console.log('File written');
})
.catch((err) = > {
console.error(err);
});
Copy the code
Async/Await is simpler than Promises
Bad:
import { get } from 'request-promise';
import { writeFile } from 'fs-promise';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) = > {
return writeFile('article.html', response);
})
.then((a)= > {
console.log('File written');
})
.catch((err) = > {
console.error(err);
});
Copy the code
Good:
import { get } from 'request-promise';
import { writeFile } from 'fs-promise';
async function getCleanCodeArticle() {
try {
const response = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
await writeFile('article.html', response);
console.log('File written');
} catch(err) {
console.error(err); }}Copy the code
↑ Back to top
Error handling
Do not ignore throwing exceptions
Bad:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Copy the code
Good:
try {
functionThatMightThrow();
} catch (error) {
// This option is more intuitive than console.log
console.error(error);
// You can also alert the user in the interface
notifyUserOfError(error);
// The exception can also be passed back to the server
reportErrorToService(error);
// Other custom methods
}
Copy the code
Don’t forget to stand out in Promises
Bad:
getdata()
.then((data) = > {
functionThatMightThrow(data);
})
.catch((error) = > {
console.log(error);
});
Copy the code
Good:
getdata()
.then((data) = > {
functionThatMightThrow(data);
})
.catch((error) = > {
// This option is more intuitive than console.log
console.error(error);
// You can also alert the user in the interface
notifyUserOfError(error);
// The exception can also be passed back to the server
reportErrorToService(error);
// Other custom methods
});
Copy the code
↑ Back to top
Code style.
Code style is subjective, and debating which is good and which is not is a waste of life. There are a lot of tools out there that automate your code style, so just pick one that you like, and let’s talk about some of the non-automated parts.
Constant capital
Bad:
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
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 {}
Copy the code
Good:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ['Back In Black'.'Stairway to Heaven'.'Hey Jude'];
const ARTISTS = ['ACDC'.'Led Zeppelin'.'The Beatles'];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}
Copy the code
Call first and call later
Just like we read a newspaper article, we read it from top to bottom, so we write the function declaration in front of the function call for easy reading.
Bad:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getManagerReview() {
const manager = this.lookupManager();
}
getSelfReview() {
// ...}}const review = new PerformanceReview(employee);
review.perfReview();
Copy the code
Good:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
getManagerReview() {
const manager = this.lookupManager();
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getSelfReview() {
// ...}}const review = new PerformanceReview(employee);
review.perfReview();
Copy the code
↑ Back to top
annotation
Only the business logic needs annotations
More code comments are not always better.
Bad:
function hashIt(data) {
// This is the initial value
let hash = 0;
// The length of the array
const length = data.length;
// loop array
for (let i = 0; i < length; i++) {
// Get the character code
const char = data.charCodeAt(i);
/ / modify the hash
hash = ((hash << 5) - hash) + char;
// Convert to a 32-bit integerhash &= hash; }}Copy the code
Good:
function hashIt(data) {
let hash = 0;
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
// Convert to a 32-bit integerhash &= hash; }}Copy the code
Delete commented code
Git exists to save your old code, so get rid of the commented code.
Bad:
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Copy the code
Good:
doStuff();
Copy the code
↑ Back to top
Don’t keep a Journal
Remember you have Git! Git log can do this for you.
Bad:
/** * 2016-12-20: delete XXX * 2016-10-01: improve XXX * 2016-02-03: delete type check in line 12 * 2015-03-14: add a merge method */
function combine(a, b) {
return a + b;
}
Copy the code
Good:
function combine(a, b) {
return a + b;
}
Copy the code
↑ Back to top
Comments do not need to be highlighted
Comment highlighting, instead of being a reminder, distracts you from reading the code.
Bad:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
menu: 'foo'.nav: 'bar'
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
// ...
};
Copy the code
Good:
$scope.model = {
menu: 'foo'.nav: 'bar'
};
const actions = function() {
// ...
};
Copy the code
↑ Back to top
Thanks for reading!
ref
Translated from clean-code-javascript by ryanmcdermott with some modifications.