Перевод книги Райана Макдермота clean-code-javascript
В javascript отсутствуют ключевые слова private и public, что усложняет реализацию классов. Лучше использовать геттеры и сеттеры для доступа к свойствам объекта, чем напрямую к ним обращаться. Вы спросите «Зачем?». Вот несколько причин:
Плохо:
Хорошо:
Это возможно с помощью замыканий.
Плохо:
Хорошо:
Колбеки приводят к чрезмерной вложенности и плохой читаемости кода.
Плохо:
Хорошо:
Промисы очень хорошая альтернатива колбекам, но в ES2017 / ES8 спецификации появился аsync/аwait, который предлагает ещё лучшее решение. Все, что вам нужно, это написать функцию с префиксом async, внутри которой вы можете писать вашу асинхронную логику императивно. аsync/аwait можно использовать прямо сейчас при помощи babel.
Плохо:
Хорошо:
Бросать ошибки — хорошее решение! Это означает, что во время выполнения вы будете знать, если что-то пошло не так. Вы сможете остановить выполнение вашего приложения в нужный момент и видеть место ошибки с помощью стек трейса в консоли.
Ничего не делая с пойманной ошибкой, вы теряете возможность исправить ошибку или отреагировать на неё когда-либо. Вывод ошибки в консоль(console.log(error)) не дает лучшего результата, потому что ошибка может потеряться среди выводимых записей в консоль. Если вы заворачиваете кусок кода в try / catch, значит вы предполагаете возникновение ошибки. В таком случае вы должны иметь запасной план.
Плохо:
Хорошо:
Вы не должны игнорировать ошибки, возникшие в промисе, по той же причине, что отловленные ошибки в try / catch.
Плохо:
Хорошо:
Оглавление:
- Введение
- Переменные
- Функции
- Классы
- Объекты и структуры данных. Асинхронность. Обработка ошибок.
- Тестирование. Форматирование. Комментарии.
Объекты и структуры данных
Используйте геттеры и сеттеры
В javascript отсутствуют ключевые слова private и public, что усложняет реализацию классов. Лучше использовать геттеры и сеттеры для доступа к свойствам объекта, чем напрямую к ним обращаться. Вы спросите «Зачем?». Вот несколько причин:
- Если вы хотите реализовать больше, чем просто доступ к свойству, вам нужно поменять реализацию в одном месте, а не по всему коду.
- Валидацию легко реализовать на уровне реализации сеттера
- Инкапсуляция внутреннего состояния объекта
- Легко добавить логирование и обработку ошибок на уровне геттеров и сеттеров
- Наследуя этот класс, вы можете переопределить функциональность по умолчанию
- Вы можете лениво подгружать свойства вашего объекта, например, с сервера.
Плохо:
class BankAccount {
constructor() {
this.balance = 1000;
}
}
const bankAccount = new BankAccount();
// Покупаем, например, обувь...
bankAccount.balance -= 100;
Хорошо:
class BankAccount {
constructor(balance = 1000) {
this._balance = balance;
}
// It doesn't have to be prefixed with `get` or `set` to be a getter/setter
set balance(amount) {
if (this.verifyIfAmountCanBeSetted(amount)) {
this._balance = amount;
}
}
get balance() {
return this._balance;
}
verifyIfAmountCanBeSetted(val) {
// ...
}
}
const bankAccount = new BankAccount();
// Покупаем, например, обувь...
bankAccount.balance -= shoesPrice;
// получаем баланс
let balance = bankAccount.balance;
Реализуйте приватные свойства ваших объектов
Это возможно с помощью замыканий.
Плохо:
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
Хорошо:
const Employee = function (name) {
this.getName = function getName() {
return 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: John Doe
Асинхронность
Используйте промисы вместо колбеков
Колбеки приводят к чрезмерной вложенности и плохой читаемости кода.
Плохо:
const request = require('request');
const fs = require('fs');
const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';
request.get(url, (requestErr, response) => {
if (requestErr) {
console.error(requestErr);
} else {
fs.writeFile('article.html', response.body, (writeErr) => {
if (writeErr) {
console.error(writeErr);
} else {
console.log('File written');
}
});
}
});
Хорошо:
const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');
const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';
requestPromise.get(url)
.then((response) => {
return fsPromise.writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});
Async/Await делает код чище, чем промисы
Промисы очень хорошая альтернатива колбекам, но в ES2017 / ES8 спецификации появился аsync/аwait, который предлагает ещё лучшее решение. Все, что вам нужно, это написать функцию с префиксом async, внутри которой вы можете писать вашу асинхронную логику императивно. аsync/аwait можно использовать прямо сейчас при помощи babel.
Плохо:
const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');
const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';
requestPromise.get(url)
.then((response) => {
return fsPromise.writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});
Хорошо:
const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');
async function getCleanCodeArticle() {
try {
const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';
const response = await requestPromise.get(url);
await fsPromise.writeFile('article.html', response);
console.log('File written');
} catch(err) {
console.error(err);
}
}
Обработка ошибок
Бросать ошибки — хорошее решение! Это означает, что во время выполнения вы будете знать, если что-то пошло не так. Вы сможете остановить выполнение вашего приложения в нужный момент и видеть место ошибки с помощью стек трейса в консоли.
Не игнорируйте отловленные ошибки
Ничего не делая с пойманной ошибкой, вы теряете возможность исправить ошибку или отреагировать на неё когда-либо. Вывод ошибки в консоль(console.log(error)) не дает лучшего результата, потому что ошибка может потеряться среди выводимых записей в консоль. Если вы заворачиваете кусок кода в try / catch, значит вы предполагаете возникновение ошибки. В таком случае вы должны иметь запасной план.
Плохо:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Хорошо:
try {
functionThatMightThrow();
} catch (error) {
// Один из вариантов (более заметный, чем console.log):
console.error(error);
// Другой вариант - известить пользователя про ошибку:
notifyUserOfError(error);
// И еще вариант - отправить ошибку на сервер :
reportErrorToService(error);
// Или используйте все три варианта!
}
Не игнорируйте ошибки, возникшие в промисах
Вы не должны игнорировать ошибки, возникшие в промисе, по той же причине, что отловленные ошибки в try / catch.
Плохо:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
console.log(error);
});
Хорошо:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
// Один из вариантов (более заметный, чем console.log):
console.error(error);
// Другой вариант - известить пользователя про ошибку:
notifyUserOfError(error);
// И еще вариант - отправить ошибку на сервер :
reportErrorToService(error);
// Или используйте все три варианта!
});