Чистый код. Часть 2
Продолжаем читать книгу «Чистый код» Роберта Мартина. В прошлый раз мы обсудили именование переменных, функции и комментарии. Сегодня рассмотрим главы 5–8.
Глава 5. Форматирование
Коротко:
- делите код на вертикальные блоки;
- связанные друг с другом сущности держите рядом;
- автоматизируйте проверку форматирования.
Форматирование кода помогает его быстрее считывать. Вертикальное и горизонтальное форматирование равноценно важны.
Разделяйте код на смысловые блоки с помощью переносов строк. Помните, что связанные по смыслу строк следует держать ближе друг к другу, чем не связанные:
// плохо
import _ from 'lodash'
import userTemplate from './templates'
const transformUsersList = (users) => {
return _.chain(users).filter(user => !!user.id).map(user => ({...userTemplate, id: user.id, name: user.name})).value()
}
export default transformUsersList
// хорошо
import _ from 'lodash'
import userTemplate from './templates'
const transformUsersList = (users=[]) => {
return _
.chain(users)
.filter(user => !!user.id)
.map(user => ({
...userTemplate,
id: user.id,
name: user.name
}))
.value()
}
export default transformUsersList
Объявляйте переменные настолько близко к месту их использования, насколько возможно. Так будет проще понять, что с переменной происходит, и как она изменяется. То же с функциями: если одна вызывает другую, они должны быть расположены рядом. (Это не всегда возможно, но постараться стоит.)
Если одна функция вызывает другую, первая должна быть объявлена сверху:
// плохо
const showUsersList = () => {
/* ... */
}
const fetchUsersList = () => {
/* ... */
// вызывает showUsersList
}
const handleButtonClick = () => {
/* ... */
// вызывает fetchUsersList
}
// хорошо
const handleButtonClick = () => {
/* ... */
// вызывает fetchUsersList
}
const fetchUsersList = () => {
/* ... */
// вызывает showUsersList
}
const showUsersList = () => {
/* ... */
}
В горизонтальном форматировании следует придерживаться соглашений конкретного языка. Если есть задокументированные соглашения — пользуйтесь ими, если нет — лучшими практиками.
Горизонтальное выравнивание по большей части бесполезно. Если добавится переменная с большим количеством символов в названии, придётся изменять выравнивание:
// бессмысленно
const users = []
const fakeUsers = []
// добавилась другая переменная, форматирование надо менять
const users = []
const fakeUsers = []
const confirmedUsers = []
Лучше подобрать не выравнивание, а оптимальное количество переменных. Если требуется, то вынести какую-то часть кода в отдельную функцию.
Наблюдение за форматированием надо отдавать роботам. Заведите в проекте линтеры, которые будут следить за переносами, индентацией и прочим.
Глава 6. Объекты и структуры данных
Коротко:
- объекты и структуры данных — не одно и то же;
- слабая связанность — хорошо.
Разница между объектами и структурами данных в том, что объекты прячут данные за абстракциями и предоставляют функции для работы с данными. Структуры данных наоборот данные не прячут.
Поэтому в процедурный код (использующий структуры данных) легче добавлять новые функции без изменения структуры. В объектно-ориентированный — легче добавлять классы без изменения существующих функций.
По закону Деметры объект-клиент должен избегать вызовов методов объектов, внутренних членов, возвращенных методом объекта-сервиса. Грубо говоря цепочки методов (паровозики) нарушают закон:
// плохо
userInstance.getName().getInitials()
// хорошо
userInstance.getInitials()
Глава 7. Обработка ошибок
Коротко:
- используйте исключения вместо кодов ошибок;
- передавайте контекст выполнения вместе с ошибкой;
- обработка ошибок должна быть максимально отделённой от бизнес-логики.
Обработка ошибок — важная часть программы, но она не должна сбивать с толку и должна быть прозрачной.
По возможности используйте исключения языка вместо кодов ошибок, это избавит вас от кучи флагов и проверок. Начинайте писать код с try-catch
. Это поможет понять, как должна вести себя функция, какой у неё должен быть интерфейс, какие могут возникнуть исключения.
Передавайте контекст вместе с ошибкой: источник, окружение, некоторые значения переменных. Это сократит время дебагинга. При создании собственных исключений в первую очередь думайте о том, как они будут отловлены.
При обработке сторонних API старайтесь минимизировать зависимость от них. Обработка ошибок должна быть максимально отделённой от бизнес-логики.
Глава 8. Разграничения
Коротко:
- перед использованием сторонних библиотек разберитесь, как они работают;
- пишите адаптеры для стороннего кода и кода, которого ещё не существует;
- чётко разграничивайте обязанности своего кода и стороннего.
Перед тем, как использовать чей-то код в своём проекте, надо понять, как этот код работает. Как только вы возьмёте его к себе в проект, все его проблемы станут вашими. Изучить работу стороннего кода помогают тесты. В них вызывается какой-то метод стороннего API, и описывается ожидаемый ответ.
Если в вашем приложении есть модули, которые должны работать с ещё не существующими модулями, описывайте для них удобный вам интерфейс. Если он не совпадёт с настоящим интерфейсом, напишите для него адаптер.
В следующих главах обсудим:
- юнит-тесты и три закона TDD;
- работу с классами;
- масштабирование систем.