Чистый код. Часть 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;
  • работу с классами;
  • масштабирование систем.

Ссылки по теме: