Рига, март 2018

Первое, что бросается в глаза в Риге — какое‑то дикое количество казино и их рекламы. Они везде: на окраинах, в центре, в старом городе, в подземном переходе.

Казино рядом с вокзалом
Казино в центре города
Казино в старом городе
Реклама в подземном переходе
Ещё казино
И ещё
Сколько ж можно

При этом старый город выглядит красиво. Убрать бы только лишние вывески.

Рижская улица вечером
Рядом с собором
Улицы днём
Здания в Риге

Чёрные дома — отдельный кайф:

Чёрный дом

Площади:

Одна из площадей — 1
Одна из площадей — 2

Домский собор:

Домский собор

Один из «Трёх братьев»:

Один из «Трёх братьев»

Такое вот тоже есть:

Новая архитектура

С церкви святого Петра открывается хороший вид:

Вид на город с церкви святого Петра — 1
Вид на город с церкви святого Петра — 2

Церковь изнутри:

Церковь святого Петра изнутри

В центре города неплохой парк:

Парк в центре – 1
Парк в центре — 2

Из музеев советую военный музей:

Экспозиция военного музея — 1
Экспозиция военного музея — 2
Экспозиция военного музея — 3

И «Уголовой дом» — бывшее здание КГБ:

«Уголовой дом» — бывшее здание КГБ
«Уголовой дом» — бывшее здание КГБ

В зоопарке всё плохо с навигацией. Трудно найтись, легко потеряться, вот это всё.

Рижский зоопарк — 1
Рижский зоопарк — 2
Рижский зоопарк — 3
Рижский зоопарк — 4
Рижский зоопарк — 5

Дом с котиком на крыше:

Дом с котиком на крыше

Наверное, самый милый столбик, который я видел:

Парковочный столб с портретом

Да и в целом всё довольно приятно:

Кафе
Улица и велосипед

«Эффективная работа с легаси‑кодом», часть 3

В прошлый раз мы поговорили о добавлении фич, ТДД и зависимостях. Сегодня — об изменении непонятного кода, гигантских классах и методах.

Глава 16. Я не понимаю этот код. Как мне его менять

Самый эффективный способ понять легаси — переорганизовать его. Создайте новую ветку в гите, забудьте о тестах и извлекайте методы, переносите переменные, делайте что угодно: это даст представление о том, как код работает. С этим знанием будет проще по‑настоящему менять этот код потом. Удаляйте неиспользуемый и закомментированный код.

Глава 17. У приложения нет структуры. Что делать

Когда разработчики в команде не парятся об архитектуре, она со временем деградирует. Плохая структура приложения мешает добавлять фичи в проект, править баги и замедляет работу.

При работе с легаси может помочь приём «описание системы». Задайте себе вопрос «что из себя представляет структура системы?» и отвечайте на него так, как будто вам заранее ничего о ней не известно. Когда вы начнёте себе объяснять, как система функционирует, вы будете упрощать логику её работы. Это поможет выделить по‑настоящему важные элементы и построить идеальную схему взаимодействий внутри системы.

Глава 18. Тестовый код мне мешает

Помечайте тестовые классы или функции суффиксами .test, .spec, фиктивные объекты — .fake, .mock, .stub. Тесты также можно вынести в отдельную папку в проекте. Удобно когда структура файлов тестов повторяет структуру файлов проекта.

Глава 19. У меня не ООП. Как безопасно рефакторить код

Дилемма легаси: чтобы изменить код, надо его покрыть тестами, а чтобы покрыть тестами, его надо изменить. Эта дилемма справедлива не только для ООП. Решения те же: разрыв зависимостей, фиктивные объекты, ТДД.

Глава 20. Класс уже огромный. Не хочу, чтобы он стал больше

Если в классе 50 методов, то приходится долго вникать в его работу перед тем, как внести какие‑то изменения. Не увеличивать размер класса помогут почкование метода или класса, но это временное решение. Настоящее решение — рефакторинг.

Рефакторить большие классы нужно по принципу единой ответственности: когда одна сущность отвечает за одну конкретную задачу. Если причина для изменения какого‑то метода класса всего лишь одна — вы сделали всё правильно.

Чтобы определиться с ответственностью методов, попробуйте их сгруппировать. Методы, которые выполняют близкие задачи, должны быть в одном классе, остальные можно вынести в другой. Если в классе много приватных методов, скорее всего, их можно вынести в отдельный класс. Если вам хочется протестировать какой‑то приватный метод, то он должен быть публичным.

Обращайте внимание на действия кода, которые кажутся захардкоженными: обращение к базе, внешним объектам. Такие методы могут многое скрывать и быть слишком абстрактными. Проверьте все внутренние зависимости перед их извлечением, чтобы ничего не поломать.

Определитесь с главной ответственностью для класса. Проверьте, чтобы все методы внутри помогали достигать главной цели класса.

Глава 21. Я изменяю одинаковый код снова и снова

Один и тот же повторяющийся код можно переструктурировать по‑разному:

const func = () => {
  a(); a(); b(); a(); b(); b();
}

// может превратиться как в 
const func = () => {
  aa(); b(); a(); bb();
}

// так и в
const func = () => {
  a(); ab(); ab(); b();
}

Начните с малого и двигайтесь с оглядкой на главную цель класса или метода. По мере продвижения вам будет становиться яснее, как поступить правильнее. Когда два метода или класса выглядят почти одинаково, вынесите разницу в другие методы или классы, а прежние объедините:

class AddEmployeeCmd extends Command { 
  constructor() {
    this.name = ''
    this.address = ''

    this.header = ['some', 'sophisticated', 'data', 'structure']
    this.commandCharIndex = 42
    this.footer = ['some', 'sophisticated', 'data', 'structure']
  }
}

class LoginCommand extends Command { 
  constructor() {
    this.userName = ''
    this.password = ''

    this.header = ['some', 'sophisticated', 'data', 'structure']
    this.commandCharIndex = 44
    this.footer = ['some', 'sophisticated', 'data', 'structure']
  }
}

// мы можем вынести header и footer прямо в Command,
// потому что они одинаковые

class Command {
  constructor() {
    this.header = ['some', 'sophisticated', 'data', 'structure']
    this.footer = ['some', 'sophisticated', 'data', 'structure'] 
    // ...
  }
}

class AddEmployeeCmd extends Command { 
  constructor() {
    this.name = ''
    this.address = ''
  }
  // ...
}

class LoginCommand extends Command { 
  constructor() {
    this.userName = ''
    this.password = ''
  }
  // ...
}

У кода хороший дизайн, если нам не нужно менять много кода, чтобы добавить новую фичу.

Глава 22. Мне надо изменить гигантский метод, и я не могу написать тесты к нему

При изменении гигантских методов пользуйтесь автоматическими инструментами для рефакторинга, не меняйте код вручную. Придерживайтесь двух целей:

  • очистить логику от непонятных зависимостей;
  • добавить швы, которые помогут написать тесты для метода.

Вводите новые переменные, чтобы определить, когда программа доходит до момента, который вы собираетесь рефакторить.

// этот класс строит ДОМ-дерево,
// мы хотим отрефакторить условие добавления узла в корень дерева
// но не знаем, в какой момент это происходит

class DomBuilder {
  processNode(node) {
    // ...
    if (node.type() === 'TYPE1' 
        || node.type() === 'TYPE2' 
        || node.type() === 'TYPE3' && node.isVisible()) {
      this.root.appendChild(node)
    }
  }
}

// вводим переменную, которая поможет выявить этот момент

class DomBuilder {
  constructor() {
    this._nodeAdded = false
  }

  processNode(node) {
    // ...запутанная логика
    if (this.isBaseChild(node)) {
      this.root.appendChild(node)
      this._nodeAdded = true
    }
  }

  isBaseChild(node) {
    return node.type() === 'TYPE1' 
        || node.type() === 'TYPE2' 
        || node.type() === 'TYPE3' && node.isVisible()
  }
}

// тест на случай, когда узел должен был добавиться
// и на случай, когда не должен был

it('tests if node is base child', () => {
  const node = new Node('TYPE1')
  const builder = new DomBuilder()

  builder.processNode(node)

  expect(builder._nodeAdded).toEqual(true)
})

it('tests if node is not base child', () => {
  const node = new Node('TYPE5')
  const builder = new DomBuilder()

  builder.processNode(node)

  expect(builder._nodeAdded).toEqual(false)
})

После завершения рефакторинга и тестирования переменную распознавания можно удалить.

Ищите последовательности действий, которые можно вынести в отдельный метод. Извлекайте метод вначале в текущий класс, а потом, если понадобится — то в другой класс. Не извлекайте сразу много кода.

Глава 23. Как мне узнать, что я ничего не сломал

Используйте принцип «одна цель за раз». Перед началом работы определитесь с тем, какой нужен результат. Разбейте инструкцию на несколько шагов, за один шаг делайте только то, что необходимо для достижения цели. Все побочные задачи делайте после.

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

Что дальше

Каждая глава в третьей части книги — пример на один из методов разрыва зависимостей. Конспектировать её смысла нет, потому что придётся переписывать всё. Лучше прочтите в оригинале, рекомендую.

«Эффективная работа с легаси‑кодом», часть 2

В первой части мы рассмотрели введение, причины изменения кода, швы и инструменты автоматизированного рефакторинга. Сегодня говорим об изменении кода, когда не хватает времени, добавлении фич, ТДД и зависимостях.

Глава 6. У меня нет времени, но мне нужно что‑то поменять

Рефакторинг и тесты — это дополнительная работа. Но в будущем она упростит внесение изменений в код и поможет быстрее отлавливать баги.

Почкование метода (погуглил, в переводе книги это называется именно так, прим. автора статьи). Если надо добавить фичу, которую можно выразить в новом коде, то лучше написать этот код в новом методе, и вызывать новый метод. Новый метод можно будет покрыть тестами.

class UsersList {
  // метод отправляет уведомления об акции 
  // указанным пользователям
  notifyOfAction(users) {
    const message = 'some message to send'
    users.forEach(user => {
      this.sendEmail(user, message)
    })
  }

  // ...
}

Если мы хотим, чтобы уведомление отправлялось только совершеннолетним, то можно профильтровать список пользователей прямо в методе:

class UsersList {
  notifyOfAction(users) {
    const message = 'some message to send'
    users = users.filter(user => user.age >= 18)
    users.forEach(user => {
      this.sendEmail(user, message)
    })
  }

  // ...
}

Но таким образом мы не поймём, где заканчивается старый код, где начинается новый, и как это тестировать. Лучше вынести фильтрацию в новый метод:

class UsersList {
  notifyOfAction(users) {
    const message = 'some message to send'
    users = this.filterInAdults(users)
    users.forEach(user => {
      this.sendEmail(user, message)
    })
  }

  filterInAdults(users) {
    return users.filter(user => user.age >= 18)
  }

  // ...
}

Теперь новый метод filterInAdults можно покрыть тестами.

Алгоритм метода:

  • найти место, которое надо поменять;
  • если изменение можно вызвать как отдельный метод, то написать код вызова этого метода, добавив комментарии к нему;
  • определить все локальные переменные, необходимые для вызова и сделать их аргументами нового метода;
  • определиться со значением, которое должен возвращать новый метод;
  • создать новый метод, пользуясь ТДД;
  • убрать комментарий, заменить его на настоящий новый метод.

Минусы:

  • вы на какое‑то время оставляете исходный метод без внимания, не собираясь его тестировать или менять;
  • иногда становится непонятно, почему действия происходят в другом методе.

Плюсы:

  • новый код полностью отделён от старого;
  • возможно протестировать;
  • можно наблюдать за всеми переменными, которые меняют поведение метода.

Почкование можно применить для целого класса, а не только метода. Например, если нужно поменять формат вывода результата со строки на ХТМЛ.

Оборачивание метода (в переводе охват, прим. автора статьи). Ещё один способ добавить поведение к уже существующему коду. Метод pay класса Employee вызывает paymentDispatcher, чтобы выплатить деньги сотруднику.

class Employee { 
  pay(amount) {
    const date = // ...
    paymentDispatcher(date, amount)
  }
}

Допустим, мы хотим логировать каждую оплату. Можно добавить логирование напрямую в метод pay, но можно создать новый метод с такой же сигнатурой, и вызывать его внутри:

class Employee { 
  // новый метод со старым кодом
  dispatchPayment(amount) {
    const date = // ...
    paymentDispatcher(date, amount)
  }

  // старый метод с вызовом выделенного и добавленным логированием
  pay(amount) {
    this.logPayment(amount)
    this.dispatchPayment(amount)
  }

  // добавочный метод
  logPayment(amount) {
    // ...
  }
}

Алгоритм:

  • определить метод, который надо изменить;
  • создать новый метод с такой же сигнатурой;
  • вызвать новый метод внутри старого;
  • создать метод для нового поведения через ТДД и вызывать его в старом методе.

Плюсы:

  • старые методы не меняются, только лишь переименовываются;
  • новые функции явно не зависимы от других.

Минусы:

  • могут появиться неудачные названия методов.

Можно так же обернуть не только метод, но и класс. Для примера выше можно создать класс LoggingEmployee, который будет наследоваться от Employee, и перегрузить метод pay, добавив нужное новое поведение. По‑другому это ещё называется декоратором.

Глава 7. Изменения до бесконечности

Когда код зависит от какого‑то интерфейса, то эта зависимость обычно слабая. Менять код нужно, только если сам интерфейс поменяется. Поэтому лучше иметь зависимости от абстрактных интерфейсов, чем от конкретных классов.

Применяйте интерфейсы для разрыва зависимостей в проекте перед рефакторингом. Это занимает время, но в итоге появляются участки кода, с которыми легко работать и тестировать.

Глава 8. Как добавить фичу

Алгоритм работы по ТДД:

  • написать отказной тест;
  • написать функцию, чтобы тест перестал падать;
  • убрать дублирование кода;
  • повторить.

На примерах, первый шаг:

// пишем тест, который заведомо упадёт
it('Возвращает сумму двух однозначных чисел', () => {
  expect(sum(1, 1)).toEqual(1 + 1)
})

// функция пока пустая
const sum = (a, b) => {}

Убеждаемся, что тест падает с той причиной, которую мы ожидаем. На втором шаге:

// меняем реализацию функции или метода так, чтобы тест проходил
const sum = (a, b) => a + b

На третьем проверяем, чтобы не было дублирования и рефакторим код метода и тестов. Постепенно описываем примеры прохождения теста и примеры непрохождения. В легаси‑коде тестируемые методы и классы вначале готовят к тестированию, а потом переходят к алгоритму ТДД.

Глава 9. Я не могу протестировать этот класс

Некоторые классы трудно тестировать: дорого создавать объекты этого класса, при создании появляются сайд‑эффекты, куча параллельной работы выполняется. Самый простой способ узнать, сколько проблем доставит создание экземпляра такого класса — создать его. Попробуйте создать объект, отладчик скажет, чего не хватает для создания.

Тестовый код не обязательно должен жить по тем же стандартам, что продакшн‑код. Можно нарушать инкапсуляцию, если это делает написание тестов проще. Но тесты должны быть понятными и чистыми. Выносите повторяющийся код для тестов в сетап.

Если создание класса требует экземпляров других классов, попробуйте создать их с пустыми значениями в их конструкторах.

Если у класса есть скрытые зависимости в конструкторе, подумайте над тем, чтобы сделать их явными. Например, передавать эту зависимость в конструктор извне.

Глава 10. Я не могу выполнить этот метод в средствах тестирования

Иногда методы класса тоже трудно протестировать:

  • он приватный;
  • трудно создать аргументы для вызова этого метода;
  • метод имеет сайд‑эффекты.

Если метод приватный, надо подумать, можно ли его протестировать через публичный метод. В общем случае, если надо тестировать приватный метод, то следует сделать его публичным. Если же это парит, то, видимо, класс делает слишком много.

При наличии сайд‑эффектов стоит попробовать извлечь метод. Класс ниже создаёт элементы интерфейса и решает, какую информацию в них отображать:

class AccountDetails {
  // ...

  handlePerformedAction(event) {
    const {type} = event
    if (type === 'project activity') {
      const modal = this.createModal()
      const display = this.createElement('input')
      modal.setDescription('modal text')
      modal.show()

      let accountDescription = this.modal.getSymbol()
      accountDescription += ': '
      // ...
      display.value = accountDescription
    }
  }
}

Можно отделить код, который не зависит от интерфейса, от кода, который зависит. Выделим методы обработки команды, чтобы методы стали тестируемыми и независимыми друг от друга:

class AccountDetails {
  // ...

  handlePerformedAction(event) {
    const {type} = event
    this.performCommand(type)
  }

  performCommand(type) { 
    if (type === 'project activity') {
      this.setDescription('modal text')
      this.updateModalValue()
    }
  }

  setDescription(text) {
    const modal = this.createModal()
    modal.setDescription(text)
    modal.show()
  }

  updateModalValue() {
    const display = this.createElement('input')
    let accountDescription = this.modal.getSymbol()
    accountDescription += ': '
    // ...
    display.value = accountDescription
  }
}

Разделение команд и запросов. Метод может быть либо командным, либо запросным, но не тем и не другим одновременно. Командный — который видоизменяет состояние объекта, но не возвращает значение. Запросный возвращает значение, но не видоизменяющий объект.

Глава 11. Надо изменить код, но непонятно, какие методы тестировать

Изменение в методе вызывает цепочку изменений в других местах, которые связаны с этим методом. Если код структурирован хорошо, то схемы таких связей будут простыми.

Воздействия распространяются в коде тремя основными путями.

  • когда программа использует значения, которые возвращает метод;
  • когда метод изменяет параметры, которые ему передаются в качестве аргументов, а программа использует их после изменений;
  • когда метод меняет глобальные объекты или переменные.

Алгоритм распознавания воздействий:

  • определить метод, который надо поменять;
  • если метод возвращает значение, проверить места, где этот метод вызывается;
  • если метод меняет какие‑то параметры, проверить места, которые используют эти параметры;
  • проверить суперклассы и подклассы объекта с этим методом;
  • найти глобальные переменные, которые этот метод меняет в ходе работы.

Главы 12–13. Какие тесты писать, чтобы изменить код

Поведение легаси кода стоит принимать за аксиому: раз он так работает, значит, так надо. Поэтому поведение этого кода нужно сохранять. Характеристические тесты — тесты, которые сохраняют поведение.

Алгоритм создания характеристических тестов:

  • начать тестировать небольшой фрагмент кода;
  • написать заведомо отказной тест;
  • выявить поведение кода;
  • поправить тест так, чтобы в нём предполагалось это поведение.

В легаси могут быть ошибки. Поэтому при обнаружении странного поведения, надо узнать, баг это или фича.

Глава 14. Зависимости от библиотек меня убивают

Избегайте беспорядочного расположения прямых вызовов библиотек в своем коде.

В следующий раз

Поговорим о непонятном коде, коде без структуры, огромных классах и ситуациях, когда ничто не помогает.

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

«Эффективная работа с легаси‑кодом» Майкла Физерса

Приёмы из этой книги мне помогают в работе, поэтому я решил составить по ней конспект. Примеры в ней написаны на Джаве и С++, поэтому не всё получилось перевести на Джаваскрипт. Я постарался вытянуть наиболее важное, но советую прочесть книгу и самим. Теперь к делу.

Физерс определяет легаси‑код, как код без тестов. В таком коде невозможно предсказать, сделает ли какое‑то изменение этот код лучше или хуже.

Глава 1. Изменение софта

Менять код нужно, чтобы:

  • добавить фичу;
  • пофиксить баг;
  • улучшить дизайн кода;
  • оптимизировать использование ресурсов.

В программе самое важное — её поведение. Пользователи любят, когда мы добавляем новое, и не любят, когда мы меняем старое. При добавлении нового поведения мы неизбежно меняем старое.

// до изменения
class Player {
  addPlaylist(name, tracks) {
    ...
  }
}

// после
class Player {
  addPlaylist(name, tracks) {
    ...
  }

  deletePlaylist(name) {
    ...
  }
}

Пока новый метод нигде не вызывается, поведение не меняется. Чтобы это поведение добавить, мы выводим кнопку на экране. Из‑за этого интерфейс отрисовывается на долю секунды дольше. Это незаметно, но поведение поменялось.

Улучшение дизайна, рефакторинг, отличается тем, что не меняет поведение программы. Их цель — сделать код более читаемым, поддерживаемым и приятным.

Оптимизация похожа на рефакторинг, только рефакторим мы уже использование ресурсов, а не сам код.

Сохранить поведение неизменным трудно. Каждое изменение кода несёт риск изменения поведения. Чтобы смягчить риск, перед изменениями задавать себе три вопроса:

  • что надо поменять?
  • как узнать, что мы внесли изменения правильно?
  • как узнать, что мы не сломали остальное?

Разница между хорошими и плохими программными системами в том, что внесение изменений в хорошие не вызывает беспокойства. В плохих системах, чем дольше тянешь, тем страшнее менять в будущем.

Глава 2. Работать на обратную связь

Вносить изменения в код можно двумя путями: «запушить и молиться» и «покрыть тестами и менять». Смысл второго подхода в том, чтобы получать обратную связь постоянно и как можно быстрее. Тесты дают обратную связь, говоря, как наши изменения ломают поведение программы.

Хороший юнит‑тест:

  • быстрый;
  • помогает найти проблему быстро;
  • не имеет внешних зависимостей.

Рефакторинг легаси‑кода следует начинать с покрытия его тестами. Проблема в том, что часто у легаси куча зависимостей, из‑за которых его трудно тестировать. Возникает дилемма: чтобы изменить код, надо иметь тесты к нему, а чтобы написать к нему тесты, его надо поменять.

Алгоритм изменения легаси‑кода:

  • найти точки изменения;
  • найти точки тестирования;
  • разорвать зависимости;
  • написать тесты;
  • сделать изменения и отрефакторить.

Глава 3. Распознавание и разделение

Для тестирования нужно разрывать зависимости в коде по двум причинам:

  • если не можем получить доступ к значениям, которые код вычисляет;
  • если не можем ввести нужный фрагмент кода для выполнения в тесте.

Класс NetworkBridge получает список узлов, каждый из которых открывает сетевое соединение и общается с другими узлами:

class NetworkBridge {
  constructor(endpoints) {
    ...
  }

  formRouting(sourceId, destId) { 
    ...
  }
}

Как его тестировать? Если он связан с железом, можем ли мы себе позволить на каждый тест нагружать оборудование? Можем ли создать тестовый кластер? Есть ли на это ресурсы и время? Такие проблемы возникают, когда мы не понимаем, как выделить нужную часть и тестировать её изолированно. Здесь могут помочь фиктивные объекты.

Фиктивные объекты олицетворяют какой‑либо класс во время тестирования. Например, у нас есть класс Sale, который сканирует штрих‑коды, и выводит сообщения на экран устройства через класс Display:

class Sale {
  constructor(display) {
    this._display = display
  }

  scan(barcode) {
    // сканирует
    ...
    // выводит сообщение
    this._display.showMessage('hello world')
  }
}

class Display {
  showMessage(msg) {
    ...
  }
}

const display = new Display()
const sale = new Sale(display)

Чтобы не зависеть от конкретного оборудования, мы можем написать поддельный класс FakeDisplay:

class FakeDisplay {
  // вместо вывода на экран, будем запоминать сообщение
  // это метод, который имитирует настоящий метод класса Display
  showMessage(msg) {
    this.lastLine = msg
  }

  // и потом выводить его по требованию
  // это доп. метод, который нужен именно в тестах
  getLastLine() {
    return this.lastLine
  }
}

В тесте мы можем подменить класс, работающий с конкретным оборудованием на поддельный:

it('Выводит название товара на экран', () => {
  const fakeDisplay = new FakeDisplay()
  const saleTest = new Sale(fakeDisplay)

  saleTest.scan('1')

  expect(fakeDisplay.getLastLine).toEqual('Молоко')
})

Этот тест не упадёт, если не работает какая‑то часть в настоящем классе Display. Но мы тестируем класс Sale, а не Display, поэтому конкретно в этом тесте это не важно.

Глава 4. Швы

Шов — место в программе, где можно изменить её поведение, без редактирования кода в этом месте. Рабочий код должен быть одинаков и в продакшене и в тестах. Швы помогают разорвать зависимости и оттестировать код, без его изменения.

Чтобы использовать шов, нужно определиться с разрешающей точкой — местом, где вы решаете заменить одно поведение на другое.

Удобнее всего в объектно‑ориентированных языках использовать объектные швы, когда действие какого‑то метода подменяется на другое. Например, при создании экземпляра класса в конструкторе.

Глава 5. Инструменты автоматизированного рефакторинга

Рефакторинг — упрощение или улучшение дизайна кода без изменения поведения. Есть среды разработки, которые предлагают автоматические инструменты для рефакторинга. Они помогают переименовывать переменные, удалять лишнее, выносить код в отдельные функции или классы.

Часто программисты полагаются на них и не пишут тесты к коду, который собираются рефакторить этими инструментами. Но таким образом можно пропустить изменение поведения. Например:

// класс до рефакторинга
class Example {
  alpha = 0

  getValue() {
    this.alpha++
    return 42
  }

  doSomething() {
    let total = 0
    const val = this.getValue()
    for (let i = 0; i < 5; i++) {
      total += val
    }
  }
}

// после
class Example {
  alpha = 0

  getValue() {
    this.alpha++
    return 42
  }

  doSomething() {
    let total = 0
    for (let i = 0; i < 5; i++) {
      total += this.getValue()
    }
  }
}

Лишняя переменная исчезла, но вместе с этим alpha++ вызвалось 5 раз вместо 1. Юнит‑тесты помогут выявить это изменение.

В следующий раз

Поговорим об изменении кода, когда не хватает времени, добавлении фич, ТДД и зависимостях.

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

«Тонкое искусство пофигизма» Марка Мэнсона

Эту книгу я решил прочесть из‑за отзывов на неё: в половине говорили, что она отличается от обычных книг о мотивации и работает по другим правилам. И да, она постоянно тыкает носом в реальность, и это офигенно.

Почти каждая глава будто про меня. При этом автор рассказывает, как с этим жить, что делать и чего не делать.

Всё начинается с описания, откуда проблемы растут:

Больше покупать, больше делать… быть чем‑то большим. Нас то и дело чем‑то соблазняют. Купите новый телевизор. Проведите отпуск лучше, чем коллеги… Почему? Видимо, потому, что зацикленность на «большем, большем» хороша для бизнеса

Сразу дальше — о том, что важно на самом деле:

А ключ‑то к хорошей жизни отнюдь не во все новых и новых заботах: он — в меньшем числе забот. Заботиться нужно только о подлинном, настоящем и важном

В первый раз себя я узнал в описании тревоги и беспокойства:

Тревога снедает вас, и вы не понимаете, откуда она взялась. И тогда тревожитесь из‑за того, что тревожитесь… Тревога удвоилась. Вы тревожитесь из‑за тревоги, и это вызывает еще больше тревоги

Или вы настолько волнуетесь, как бы сделать все правильно, что вам не дает покоя сам факт волнения. Или ощущаете себя настолько виноватым за каждую ошибку, что испытываете чувство вины за чувство вины

У дедушки было паскудно на душе, но он говорил себе: «Черт возьми, я чувствую себя как коровья лепешка. Но такова жизнь. Пойду разгребать сено». А сейчас что? Вам на душе кошки насрали, а за пять минут на вас высыпались 350 фотографий, где люди абсолютно счастливы и живут в свое удовольствие

И тут же после автор напоминает:

Однажды вы умрете. Я знаю, это очевидно, но на всякий случай напоминаю — вдруг забыли. Вы и все люди, которых вы знаете, скоро умрут. В небольшое оставшееся время вы успеете позаботиться о немногом

Контраст показывает, что дичь, о которой мы паримся, вообще не заслуживает, чтобы о ней париться.

Или вот о самосовершенствовании:

Самосовершенствование и успех часто идут рука об руку. Но это не означает, что они — одно и то же

Часто автор ссылается на разницу между позитивным и негативным опытом:

Стремление к более позитивному опыту — само по себе негативный опыт. И, как ни парадоксально, принятие негативного опыта — позитивный опыт

Все в жизни, к чему стоит стремиться, добывается через негативный опыт

И точка зрения автора такая, что жизнь — она вообще про страдания, поэтому избегать его бессмысленно. Надо найти страдания по вкусу:

Сама жизнь — форма страдания. Богачи страдают из‑за богатства. Бедняки страдают из‑за бедности. Одинокие люди страдают из‑за того, что у них нет семьи. Семейные люди страдают из‑за проблем в семье…

Не надейся на жизнь без проблем… надейся лучше на жизнь, полную хороших проблем…

Куда бы вы ни пошли, вас поджидает огромная куча дерьма. И это — норма. И задача состоит не в том, чтобы убежать от дерьма. Она состоит в том, чтобы найти дерьмо, с которым вам понравится иметь дело

Счастье приходит от решения проблем. Ключевое слово здесь — «решение». Если вы убегаете от проблем или полагаете, что у вас их нет, вы будете несчастны

Вот так он это доказывает:

Такими нас создала эволюция: мы всегда живем с неудовлетворенностью и беспокойством, поскольку умеренно неудовлетворенное и беспокойное существо прилагает максимум усилий, чтобы создать новшества и выжить

…на душе хреново… так мозг сообщает вам, что некую проблему вы оставили без внимания и не решили. Иными словами, негативные эмоции — призыв к действию

…подавлять негативные эмоции — значит подавлять механизмы обратной связи, которые помогают решать проблемы… Помните: боль имеет смысл

При этом он часто одёргивает читателя, чтобы тот не понял книгу неправильно:

Когда мы говорим «Марк Мэнсон умеет наплевать и забить», мы не имеем в виду, что «Марку Мэнсону всё до лампочки». Мы имеем в виду другое: ему до лампочки, что на пути к целям его ждут неприятности, или что кого‑то раздражают поступки, которые он считает правильными, важными и достойными. Мы имеем в виду: Марк Мэнсон — из тех людей, которые напишут о себе в третьем лице лишь потому, что сочтут это правильным

Действие — не только следствие мотивации, но и его причина

А вот про тычки носом в реальность. Он говорит, что «чел, ты вообще не уникален, таких как ты — миллионы». Мне это особенно зашло:

У вас не получится играть важную и ключевую роль для одних людей, не став посмешищем и помехой для других

Однако уникальных проблем вообще не бывает! С такими проблемами, как у вас, миллионы людей жили в прошлом… Очень возможно, что такие люди есть и среди ваших знакомых. Это не умаляет проблем и не означает, что вам не должно быть больно… Это означает одно: вы не уникальны

Многие боятся оказаться середнячками: ведь если они смирятся с таким положением, они никогда ничего не достигнут, не выбьются в люди. Их жизнь не будет ничего значить. Такой менталитет опасен…

Поток крайностей заставляет нас верить, что исключительность — это норма. А поскольку обычно с нами ничего особенного не происходит, лавина сообщений об исключительном наводит тоску, отчаяние…

Технология решила старые экономические проблемы, принеся нам новые психологические проблемы. В интернете мы находим не только легкий доступ к информации, но и легкий доступ к неуверенности, стыду и сомнениям в себе

Когда вы полагаете, что именно ваш самолет разобьется, или что именно ваш проект все назовут идиотским, или что именно вас все высмеют и растопчут, вы по сути говорите себе: «Я — исключение из правила, я не такой, как все остальные; я уникальный и особенный». Это чистой воды нарциссизм. Вы полагаете, что именно ваши проблемы заслуживают особого обращения…

Если я считаю одно, а все остальные — другое, то намного вероятнее, что ошибаюсь я

Рецепт того, как с этим быть он описывает в виде смены ценностей:

Ценности лежат в основе всего,…что мы делаем. Если ценимое нами бесполезно, если мы неправильно выбрали критерии успеха и неудачи, то все основанное на этих ценностях… пойдет наперекосяк. Все наши мысли и чувства относительно любой ситуации в конечном счете определяются ценностями

Правильный подход к эмоциональному негативу заключается в следующем: его нужно выражать социально приемлемым и здоровым образом; его нужно выражать с учетом своих ценностей

Хорошие ценности: основаны на реальности; социально конструктивны; непосредственны и контролируемы

Чтобы поменять ценности, надо думать и задавать вопросы:

Что, если я ошибаюсь?

Что означала бы моя неправота?

Признание неправоты создаст для меня и других людей большие или меньшие проблемы, чем сейчас?

…нужно сначала усомниться в нынешних ценностях. Нужно мысленно дистанцироваться от них, увидеть их недостатки и предрассудки, их противоречие фактам; взглянуть в лицо собственному невежеству и признать его масштабы

Надо не быть уверенным, а настраивать себя так: жизнь покажет

Он говорит, что контролировать ситуацию мы не можем, так как всегда есть слишком много внешних факторов. Но мы можем влиять на своё отношение к ней:

Объективная правда о ситуации не так важна, как отношение к ней, ее анализ и оценка… Мы в состоянии контролировать смысл проблем, выбирая взгляд на них и критерий, по которому мы будем их оценивать

…мы лично несем ответственность за все, что происходит в нашей жизни, каковы бы ни были внешние обстоятельства. Да, события не всегда подконтрольны нам. Но мы всегда можем контролировать свое понимание этих событий и свою реакцию на них… Зачастую единственная разница между болезненной проблемой и приятной проблемой состоит в том, что последнюю мы выбрали сами и готовы нести за нее ответственность

А вот ещё про отношения и умение сказать «нет»:

Когда вы идете на жертву ради партнера, вы должны делать это потому, что вам хочется, а не потому, что вы чувствуете себя обязанным или боитесь последствий отказа. Если ваш партнер идет на жертву ради вас, она должна быть основана на реальном желании, а не быть следствием манипуляции с помощью гнева или вины. Деяния любви имеют смысл лишь тогда, когда совершаются без условий и ожиданий

Только в нездоровых отношениях люди пытаются решать проблемы друг друга, чтобы самим ощущать душевный комфорт. В здоровых же отношениях люди решают собственные проблемы из заботы друг о друге

Если близкие люди не способны открыто и честно разобраться со своими разно­гласиями, значит, отношения основаны на лжи и манипуляции и постепенно станут токсичными

Ну и резюмирует он всё это снова отсылкой к смерти:

…всякий жизненный смысл формируется подспудным желанием не умирать

Мне книга понравилась. Это одна из тех, которые я буду перечитывать время от времени. Рекомендую.

Похоже книги:

Статьи по теме:

Раньше ↓