Единственный способ проверить качество вашего кода: сколько раз люди говорят f*k, когда смотрят на ваш код.
Качество кода прямо пропорционально его чистоте. Чистый код не только надежен по качеству, но и закладывает хорошую основу для последующего обслуживания и обновлений.
Эта статья не руководство по стилю кода, она посвящена коду.可读性
,复用性
,扩展性
Проводить исследования.
Мы обсудим несколько аспектов:
- Переменная
- функция
- Объекты и структуры данных
- Добрый
- SOLID
- тестовое задание
- асинхронный
- обработка ошибок
- стиль кода
- Примечания
Переменная
Назовите переменные осмысленными и часто используемыми словами
Bad:
const yyyymmdstr = moment().format('YYYY/MM/DD');
Good:
const currentDate = moment().format('YYYY/MM/DD');
быть единым
У одного и того же проекта может быть три разных имени для получения информации о пользователе. Должен быть последовательным, если вы не знаете, как его назвать, перейдите кcodelfВыполните поиск, чтобы узнать, как называются другие.
Bad:
getUserInfo();
getClientData();
getCustomerRecord();
Good:
getUser()
Каждая константа должна быть названа
Можно использоватьbuddy.jsилиESLintОбнаружение безымянных констант в коде.
Bad:
// 三个月之后你还能知道 86400000 是什么吗?
setTimeout(blastOff, 86400000);
Good:
const MILLISECOND_IN_A_DAY = 86400000;
setTimeout(blastOff, MILLISECOND_IN_A_DAY);
можно описать
Новая переменная генерируется из переменной, и эту новую переменную также нужно назвать, то есть, когда вы видите каждую переменную, вы с первого взгляда будете знать, что она делает.
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]);
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);
прямо в точку
Bad:
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// 需要看其他代码才能确定 'l' 是干什么的。
dispatch(l);
});
Good:
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});
Избегайте бессмысленных префиксов
Если вы создаете объект car, нет необходимости называть его цвет carColor.
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';
}
использовать по умолчанию
Bad:
function createMicrobrewery(name) {
const breweryName = name || 'Hipster Brew Co.';
// ...
}
Good:
function createMicrobrewery(name = 'Hipster Brew Co.') {
// ...
}
функция
Чем меньше параметров, тем лучше
Если параметров больше двух, используйтеES2015/ES6
синтаксис деструктурирования, независимо от порядка аргументов.
Bad:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
Good:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});
делать только одно
Это давнее правило в программной инженерии. Строгое следование этому правилу сделает ваш код более читабельным и простым для рефакторинга. Если это правило будет нарушено, код будет сложно протестировать или использовать повторно.
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();
}
Как подсказывает название
Глядя на имя функции, вы должны знать, что она делает.
Bad:
function addToDate(date, month) {
// ...
}
const date = new Date();
// 很难知道是把什么加到日期中
addToDate(date, 1);
Good:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);
Требуется только один уровень абстракции
Если функция слишком много вложена, это затруднит ее повторное использование и тестирование.
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...
});
}
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;
}
удалить повторяющийся код
Часто это одна и та же функция, но из-за одного или двух отличий приходится писать две почти идентичные функции.
Для оптимизации повторяющегося кода требуется сильная способность к абстракции, неправильная абстракция лучше, чем повторяющийся код. Итак, в абстрактном процессе должно следоватьSOLID
в общем(SOLID
что это такое? Об этом позже).
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 'develop':
data.githubLink = employee.getGithubLink();
break
case 'manager':
data.portfolio = employee.getMBAProjects();
break
}
render(data);
})
}
Объект устанавливает свойства по умолчанию
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',
// 'body' key 缺失
buttonText: 'Send',
cancellable: true
};
function createMenu(config) {
config = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
// config 就变成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);
Не передавать параметр флага
Оценка логики выполнения по истинности или ложности флага нарушает принцип, согласно которому функция выполняет одно действие.
Bad:
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
Good:
function createFile(name) {
fs.create(name);
}
function createFileTemplate(name) {
createFile(`./temp/${name}`)
}
Как избежать побочных эффектов (часть 1)
Функция получает значение и возвращает новое значение, а другие действия называются побочными эффектами, например изменение глобальных переменных, выполнение операций ввода-вывода с файлами и т. д.
Когда функция требует побочных эффектов, таких как операции ввода-вывода с файлами, не используйте несколько функций/классов для операций с файлами, а используйте только одну функцию/класс для их обработки. То есть побочные эффекты нужно обрабатывать только в одном месте.
Три ловушки побочных эффектов: изменение типов переменных данных по желанию, свободное совместное использование состояния без структур данных и отсутствие работы с побочными эффектами в едином месте.
Bad:
// 全局变量被一个函数引用
// 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];
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'];
Как избежать побочных эффектов (часть вторая)
В JavaScript примитивные типы передаются по присваиванию, а объекты и массивы передаются по ссылке. Возьмем передачу по ссылке в качестве примера:
Предположим, мы пишем корзину, которая проходитaddItemToCart()
Способ добавить товар в корзину, изменить购物车数组
. позвони в это времяpurchase()
метод покупки, благодаря передаче по ссылке, приобретенный购物车数组
Точно последние данные.
Выглядит нормально, верно?
Если сеть отключается, когда пользователь нажимает кнопку «Купить»,purchase()
Метод вызывается неоднократно, и в то же время пользователь добавляет новые элементы, после чего сеть восстанавливается. Такpurchase()
способ получить购物车数组
неправильно.
Чтобы избежать этой проблемы, нам нужно клонировать каждый раз, когда мы добавляем продукт.购物车数组
и вернуть новый массив.
Bad:
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};
Good:
const addItemToCart = (cart, item) => {
return [...cart, {item, date: Date.now()}]
};
Не пишите глобальные методы
В JavaScript никогда не загрязняйте глобальные объекты, что может привести к непредсказуемым ошибкам в рабочей среде. Например, если выArray.prototype
добавить новыйdiff
метод определения разницы между двумя массивами. И ваш коллега собирается сделать что-то подобное, но егоdiff
Метод используется для определения разницы между первыми элементами двух массивов. Очевидно, что ваши методы будут конфликтовать.При возникновении таких проблем мы можем использовать синтаксис ES2015/ES6 для их исправления.Array
расширять.
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));
}
}
Я предпочитаю функциональное программирование императивному
Функциональное программирование переменных может сделать логику кода более понятной, элегантной и простой для тестирования.
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;
}
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)
Инкапсулировать условный оператор
Bad:
if (fsm.state === 'fetching' && isEmpty(listNode)) {
// ...
}
Good:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
Старайтесь не использовать условные предложения «не»
Bad:
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}
Good:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
Избегайте использования условных операторов
В: Невозможно писать код без условных операторов.
О: Большинство сценариев можно заменить полиморфизмом.
В: Можно использовать полиморфизм, но почему нельзя использовать условные операторы?
A: Чтобы сделать код более лаконичным и читабельным, если в вашей функции есть условное суждение, это означает, что ваша функция делает больше одной вещи, нарушая принцип единственной функции.
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 {
// ...
}
// 波音777
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();
}
}
Избегайте проверки типов (часть 1)
JavaScript является нетипизированным, что означает, что вы можете передавать параметры любого типа.Эта степень свободы очень сбивает с толку, и вы будете автоматически проверять тип. Подумайте об этом, вам действительно нужно проверять тип или что-то не так с вашим дизайном API?
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'));
}
Избегайте проверки типов (часть 2)
Если вам нужно выполнить статическую проверку типов, таких как строки, целые числа и т. д., рекомендуется 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;
}
Не переоптимизируйте
Современные браузеры сделали большую оптимизацию в нижней части. Многие схемы оптимизации в прошлом неэффективны и будут тратить ваше время. Хотите знать, какой контент оптимизируется современными браузерами,пожалуйста, нажмите здесь.
Bad:
// 在老的浏览器中,由于 `list.length` 没有做缓存,每次迭代都会去计算,造成不必要开销。
// 现代浏览器已对此做了优化。
for (let i = 0, len = list.length; i < len; i++) {
// ...
}
Good:
for (let i = 0; i < list.length; i++) {
// ...
}
Удалить устаревший код
Много раз некоторый код больше не нужен, но я беспокоюсь, что он будет использоваться в будущем, поэтому я не хочу его удалять.
Если вы забудете об этом, код останется там навсегда.
Не стесняйтесь удалять его, вы можете найти его в исторической версии кодовой базы.
Bad:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Good:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Объекты и структуры данных
использоватьget
,set
методы работы с данными
Это может принести много преимуществ, таких как регистрация при работе с данными, что удобно для отслеживания ошибок;set
Данные легко проверить, когда...
Bad:
function makeBankAccount() {
// ...
return {
balance: 0,
// ...
};
}
const account = makeBankAccount();
account.balance = 100;
Good:
function makeBankAccount() {
// 私有变量
let balance = 0;
function getBalance() {
return balance;
}
function setBalance(amount) {
// ... 在更新 balance 前,对 amount 进行校验
balance = amount;
}
return {
// ...
getBalance,
setBalance,
};
}
const account = makeBankAccount();
account.setBalance(100);
использовать приватные переменные
Замыкания могут использоваться для создания приватных переменных.
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
Good:
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
Добрый
использовать класс
До ES2015/ES6 не было синтаксиса классов, а моделировать классы можно было только в виде конструкторов, что было очень плохо читабельно.
Bad:
// 动物
const Animal = function(age) {
if (!(this instanceof Animal)) {
throw new Error('Instantiate Animal with `new`');
}
this.age = age;
};
Animal.prototype.move = function move() {};
// 哺乳动物
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() {};
// 人类
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() {};
Good:
// 动物
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() {};
}
цепной вызов
Этот шаблон весьма полезен и может быть найден во многих библиотеках, таких как jQuery, Lodash и т.д. Это делает ваш код чистым и элегантным. Это также очень просто реализовать, просто верните это в конце метода класса.
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();
Не злоупотребляйте наследованием
Наследованием часто злоупотребляют, что приводит к ухудшению читабельности.Чтобы выяснить отношения между двумя классами, тот, который выражается наследованием, принадлежит отношению, а не отношению включения, например Человек->Животное против Пользователь->Сведения о пользователе.
Bad:
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
// ...
}
// TaxData(税收信息)并不是属于 Employee(雇员),而是包含关系。
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);
}
// ...
}
SOLID
SOLID — это сочетание первых букв нескольких слов соответственно单一功能原则
,开闭原则
,里氏替换原则
,接口隔离原则
так же как依赖反转原则
.
принцип единой функции
Если класс делает слишком много вещей, его будет сложно поддерживать позже. Мы должны уточнить обязанности и выполнять свои собственные обязанности, чтобы уменьшить взаимозависимость.
Bad:
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}
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()) {
// ...
}
}
}
}
принцип открыто-закрыто
«Открытый» означает, что все классы, модули и функции должны быть расширяемыми, а «закрытый» означает, что их нельзя изменять. То есть вы можете добавлять новые функции, но не изменять исходный код.
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) => {
// 传递 response 并 return
});
} else if (this.adapter.name === 'httpNodeAdapter') {
return makeHttpCall(url).then((response) => {
// 传递 response 并 return
});
}
}
}
function makeAjaxCall(url) {
// 处理 request 并 return promise
}
function makeHttpCall(url) {
// 处理 request 并 return promise
}
Good:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = 'ajaxAdapter';
}
request(url) {
// 处理 request 并 return promise
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = 'nodeAdapter';
}
request(url) {
// 处理 request 并 return promise
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then((response) => {
// 传递 response 并 return
});
}
}
Принцип замены Лисков
Название очень обманчиво, но правда очень проста, то есть подкласс не должен переопределять метод суперкласса.
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;
}
}
// 正方形
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);
Good:
class Shape {
setColor(color) {
// ...
}
render(area) {
// ...
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Shape {
constructor(length) {
super();
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);
Принцип разделения интерфейса
В JavaScript почти нет концепции интерфейсов, поэтому этот принцип используется редко. Официальное определение гласит, что «клиент не должен зависеть от ненужного ему интерфейса», то есть интерфейс минимизируется и интерфейс развязывается.
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.
// ...
});
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() {}
}
});
Принцип инверсии зависимости
Всего два момента:
- Модули высокого уровня не могут зависеть от модулей низкого уровня, они зависят от абстрактных интерфейсов.
- Абстрактный интерфейс не может зависеть от конкретной реализации, а конкретная реализация зависит от абстрактного интерфейса.
Подводя итог в двух словах, расцепление.
Bad:
// 库存查询
class InventoryRequester {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
// 库存跟踪
class InventoryTracker {
constructor(items) {
this.items = items;
// 这里依赖一个特殊的请求类,其实我们只是需要一个请求方法。
this.requester = new InventoryRequester();
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();
Good:
// 库存跟踪
class InventoryTracker {
constructor(items, requester) {
this.items = items;
this.requester = requester;
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
// HTTP 请求
class InventoryRequesterHTTP {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
// webSocket 请求
class InventoryRequesterWS {
constructor() {
this.REQ_METHODS = ['WS'];
}
requestItem(item) {
// ...
}
}
// 通过依赖注入的方式将请求模块解耦,这样我们就可以很轻易的替换成 webSocket 请求。
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterHTTP());
inventoryTracker.requestItems();
тестовое задание
По мере роста проекта и увеличения временной шкалы какой-то старый код может не трогаться по полгода, если он выйдет в интернет в это время, вы уверены, что эта часть кода будет нормально работать? Охват тестами прямо пропорционален вашей уверенности.
PS: если вам сложно тестировать код, вам следует оптимизировать его.
Упрощение
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);
});
});
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);
});
});
асинхронный
больше никаких обратных вызовов
Никто не хочет смотреть на код вложенных обратных вызовов, замените обратные вызовы промисами.
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');
}
});
}
});
Good:
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) => {
return writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});
Async/Await более лаконичен, чем 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(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});
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);
}
}
обработка ошибок
Не игнорируйте создание исключений
Bad:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Good:
try {
functionThatMightThrow();
} catch (error) {
// 这一种选择,比起 console.log 更直观
console.error(error);
// 也可以在界面上提醒用户
notifyUserOfError(error);
// 也可以把异常传回服务器
reportErrorToService(error);
// 其他的自定义方法
}
Не забывайте создавать исключения в промисах
Bad:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
console.log(error);
});
Good:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
// 这一种选择,比起 console.log 更直观
console.error(error);
// 也可以在界面上提醒用户
notifyUserOfError(error);
// 也可以把异常传回服务器
reportErrorToService(error);
// 其他的自定义方法
});
стиль кода
Стиль кодирования субъективен, и спорить о том, что хорошо, а что нет, — пустая трата жизни. На рынке есть много инструментов, которые автоматически обрабатывают стиль кода, просто выберите тот, который вам нравится, и давайте обсудим несколько частей, которые не обрабатываются автоматически.
Постоянная капитализация
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 {}
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 {}
объявить перед вызовом
Точно так же, как мы читаем газетные статьи, мы читаем их сверху вниз, поэтому для удобства чтения пишите объявления функций перед вызовами функций.
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();
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();
Примечания
Аннотировать нужно только бизнес-логику
Комментарии к коду не чем больше, тем лучше.
Bad:
function hashIt(data) {
// 这是初始值
let hash = 0;
// 数组的长度
const length = data.length;
// 循环数组
for (let i = 0; i < length; i++) {
// 获取字符代码
const char = data.charCodeAt(i);
// 修改 hash
hash = ((hash << 5) - hash) + char;
// 转换为32位整数
hash &= hash;
}
}
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;
// 转换为32位整数
hash &= hash;
}
}
удалить закомментированный код
Цель git — сохранить ваш старый код, поэтому быстро удаляйте закомментированный код.
Bad:
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Good:
doStuff();
Не вести дневник
Помните, что у вас есть git! ,git log
может помочь вам сделать это.
Bad:
/**
* 2016-12-20: 删除了 xxx
* 2016-10-01: 改进了 xxx
* 2016-02-03: 删除了第12行的类型检查
* 2015-03-14: 增加了一个合并的方法
*/
function combine(a, b) {
return a + b;
}
Good:
function combine(a, b) {
return a + b;
}
Комментарии не нужно выделять
Подсветка комментариев не действует как подсказка, а вместо этого мешает вам читать код.
Bad:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
menu: 'foo',
nav: 'bar'
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
// ...
};
Good:
$scope.model = {
menu: 'foo',
nav: 'bar'
};
const actions = function() {
// ...
};
Спасибо за прочтение!
ref
Переведено сryanmcdermottиз"чистый код-javascript", в этой статье были внесены некоторые изменения в исходный текст.