笔记源于:https://gitee.com/mirrors_trending/clean-code-javascript?_from=gitee_search
变量
1 使用可读的名称
Bad
const yyyymmdstr = moment().format("YYYY/MM/DD");
Good
const currentDate = moment().format("YYYY/MM/DD");
2 使用同个单词表示同个变量
Bad
getUserInfo();
getClientData();
getCustomerRecord();
Good
getUser();
3 使用可理解的名称
Bad
setTimeout(blastOff, 86400000);// 不知道86400000是什么意思
Good
//通过大写的常量来声明
const MILLISECONDS_IN_A_DAY = 60 * 60 * 24 * 1000;//86400000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
4 使用具有解释性的名称
Bad
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/;
saveCityZipCode(
address.match(cityZipCodeRegex)[1],
address.match(cityZipCodeRegex)[2]
);
Good
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/;
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
5 使用明确的映射
Bad
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(l => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `l` for again?
dispatch(l);
});
Good
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(location => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});
6 不加没有意义的语境
如果你的class或者对象名称已经告诉你一些东西,你就不必在变量中重复
Bad
const Car = {
carMake: "Honda",
carModel: "Accord",
carColor: "Blue"
};
function paintCar(car) {
car.carColor = "Red";
}
Good
const Car = {
make: "Honda",
model: "Accord",
color: "Blue"
};
function paintCar(car) {
car.color = "Red";
}
7 使用默认arguments而非条件判断
Bad
function createMicrobrewery(name) {
const breweryName = name || "Hipster Brew Co.";
// ...
}
Good
function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}
函数
1 使用少量的实参
超过三个会导致组合爆炸,需要测试多次。两个是最理想的,任何超过三个的应该被合并。为了使函数具有明显的属性,可使用ES2015 / ES6解构语法
Bad
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);
Good
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});
2 函数只做一件事
当一个函数做很多事的时候,会变得组合、测试和理解
Bad
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
Good
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
3 函数名应该表明它们是做什么的
Bad
function addToDate(date, month) {
// ...
}
const date = new Date();
addToDate(date, 1);//很难判断是什么增加了
Good
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);
4 删除重复代码
有时候会因为小的差别而重新写一遍代码,这会导致很多重复代码。
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();
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}
5 使用Object.assign来设置默认对象
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);
Good
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
}
createMenu(menuConfig);
6 不要使用flag让函数完成多件事
Bad
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
Good
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}
7 避免副作用
-
如果修改一些全局变量,容易产生副作用。要避免在没有结构的对象之间分享状态、或是使用可在任何地方写入数据的类型。
Bad
let name = "Ryan McDermott"; function splitIntoFirstAndLastName() { name = name.split(" "); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott'];
Good
```js
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}
```
-
为了避免一些误触导致对象或者数组进行修改,可对其进行编辑并返回克隆,让原数据不会受到影响。
Bad
const addItemToCart = (cart, item) => { cart.push({ item, date: Date.now() }); };
Good
js const addItemToCart = (cart, item) => { return [...cart, { item, date: Date.now() }]; };
8 不要写全局函数
使用全局函数是不好的做法,因为很可能和其他库冲突。比如你想写一个显示两数组差异的diff方法,你可能会写在Array.prototype上,那它可能会和另一个执行相同操作的库发生冲突。用ES2015/ES6类并将数组扩展为全局会更好
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));
}
}
9 封装条件
Bad
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}
Good
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
10 避免否定条件
Bad
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}
Good
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
11 避免条件语句
Bad
class Airplane {
// ...
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();
}
}
}
Good
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
12 避免判断数据类型
-
使用一致的方法
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")); } }
Good
function travelToTexas(vehicle) { vehicle.move(this.currentLocation, new Location("texas")); }
-
使用TypeScript
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"); }
Good
function combine(val1, val2) { return val1 + val2; }
对象与类
1 使用getters和setters
这种方法可能会比直接在对象中查找属性要好,下面是该方法的一些优点:
- 除了获得对象属性之外,如果还想做更多的事,就不需要查找和更改代码库中的每个访问器。
- 通过set是验证变得更简单
- 封装内部表示
Bad
function makeBankAccount() {
return {
balance: 0
};
}
const account = makeBankAccount();
account.balance = 100;
Good
function makeBankAccount() {
let balance = 0;// this one is private
//getter
function getBalance() {
return balance;
}
//setter
function setBalance(amount) {
// ... 在更新数据前进行验证
balance = amount;
}
return {
getBalance,
setBalance
};
}
const account = makeBankAccount();
account.setBalance(100);
2 使用链式方法
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();
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();
3 优先考虑组合而非继承
如果是下面的情况,使用继承会更有意义,否则使用组合即可:
- 你的继承表示“是”关系,而不是“具有”关系
- 需要复用基类中的代码
- 你想通过更改基类对派生类进行全局更改
Bad
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
// 不好,因为员工“拥有”税收数据,而EmployeeTaxData不是员工的类型
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);
}
// ...
}
其他
1 单一责任原则
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()) {
// ...
}
}
}
2 开放封闭原则
应该在不更改现有代码的情况下添加新功能
Bad
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = "ajaxAdapter";
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = "nodeAdapter";
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
if (this.adapter.name === "ajaxAdapter") {
return makeAjaxCall(url).then(response => {
// 转化响应并返回
});
} else if (this.adapter.name === "nodeAdapter") {
return makeHttpCall(url).then(response => {
// 转化响应并返回
});
}
}
}
function makeAjaxCall(url) {
// 请求并返回promise
}
function makeHttpCall(url) {
// 请求并返回promise
}
Good
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = "ajaxAdapter";
}
request(url) {
// 请求并返回promise
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = "nodeAdapter";
}
request(url) {
// 请求并返回promise
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then(response => {
// 转化响应并返回
});
}
}