Выводы

Расширяемость — это две вещи:

  1. «буфер стабильности модификации»
  2. простота рефакторинга

Легкий рефакторинг

  • никогда не экспортировать значения по умолчанию
  • хорошее название
  • СУХОЙ
  • всегда удалять «мертвый код»
  • тестовое покрытие: модульное тестирование и интеграция
  • устаревший поток

Предварительные условия

Здесь я коснусь только кода, который со временем видоизменяется. Очевидно, что если мы можем предсказать, что код «статичен», не стоит много думать о его ремонтопригодности. Это эссе посвящено приложениям Java Script, хотя основные принципы применимы к каждому языку.

Расширяемость как функция времени

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

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

Итак, мы знаем, что на какое-то время наш код достаточно расширяем. Точнее говоря, это количество рефакторингов, которые он может выдержать, а не чистое время в неделях или месяцах.

Буфер стабильности мутаций

Вы можете думать об этом как об «изменении размера массива» в языках с фиксированной памятью (например, в Java). Допустим, мы выделили массив размером 10 и заполнили его 5 элементами. Нам нужно добавить еще 5 — наш буфер. Когда мы достигнем предела, размер должен быть увеличен (в два раза, в два раза меньше и т. д., в зависимости от случая). То же самое и с расширяемостью кода. Мы разрабатываем модуль, который легко мутировать в течение некоторого времени. Затем после определенного количества модификаций (добавление функций, исправление ошибок) модификация рассматриваемого модуля становится все сложнее и сложнее: нет СУХОГО кода, сложно тестировать, хаки и прочая гадость. На этом этапе мы решаем провести рефакторинг фрагмента кода, а затем легко поддерживать его в следующем цикле модификации, т. е. добавить «буфер стабильности мутаций». Его можно измерить количеством новых функций, которые он может поддерживать без существенного рефакторинга.

Рефакторинг как постоянная часть процесса

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

Никогда не добавлять экспорт по умолчанию (модули ES6). Я лично работаю в WebStorm, и мне намного проще переименовывать символы (например, функции), папки, файлы, перемещать файлы папок, использовать подсказки при импорте типов и т. д. Возможно, это связано с IDE. Но также при экспорте в виде символа (не по умолчанию) вы контролируете, как он импортируется. Конечно, можно было бы использовать псевдоним as, но это было бы дополнительной работой, а разработчики обычно ленятся думать о таких мелочах. А если это экспорт по умолчанию, то пользователь на лету создает собственное имя, которое может полностью отличаться от реального. Например, «корова» импортируется как «верблюд». Тем не менее, у этих животных есть что-то общее (например, увлечение жеванием), смысл разный. И смысл, исходящий от именования, вносит большой вклад в «стабильность мутаций».

Хорошее название. Некоторые говорят, что хорошее название — это половина успеха программы. Объяснение заключается в том, как мы думаем. Если корова импортируется как верблюд, можно подумать, что он или она может легко подойти и мягко погладить ее, хотя с верблюдом это может закончиться тем, что его облили слюной. И на самом деле, когда хорошее название проникает в код и склеивает его, приложение можно рассматривать как единое целое, ментально сформированный мир, где вам не нужно много думать о мелочах, потому что общие соглашения значительно облегчают понимание структуры приложения. Так что, прежде чем называть что-либо, будь то: переменные, функции, классы, папки, модули — всегда заранее уделяйте этому некоторое время. Лично я использую тезаурус для поиска различных синонимов и антонимов. Кроме того, мое собственное мнение — нейминг должен быть коротким. Не слишком короткий, не длинный — но сбалансированный, как все гениальное в природе.

СУХОЙ. Этот принцип хорошо известен. Я бы добавил, что хотя есть некоторые исключения из правила, в большинстве случаев его следует строго соблюдать.

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

Тестирование. Есть поговорка: Запуск приложения без тестирования — это как прыжок с парашютом без парашюта. Я не мог не согласиться. Тестирование — это то, что придает уверенности в вашем коде. Тестирование дает право собственности на ваш код. Рефакторинг становится радостным процессом без каких-либо страхов. TDD-тестирование — отличная методология. Раньше я не следовал ему строго. Теперь я бы сказал — всегда терпите неудачу первым. Событие, если вы еще ничего не написали. Сначала провалите функциональный тест — затем только создайте функцию и пройдите тест. В противном случае велика вероятность получить ложные тесты. Тестовая пирамида дает некоторое представление о возможных затратах усилий на тестирование. Из практик, основанных на поведении, мы могли уловить идею охватить в порядке важности часть кода. Например, в приложении react-redux я всегда тестировал бы любой редюсер, любую служебную функцию. Это происходит без усилий и не стоит думать о важности. Затем дело доходит до интеграционных тестов. Прежде всего, их должно быть удобно писать. Если вы ловите себя на мысли, что писать тест утомительно — сама инфраструктура тестирования требует переосмысления и рефакторинга. Тестирование всегда должно приносить удовольствие, помня о том, как это поможет вам в будущем. И затем, даже если нас устраивает наша среда тестирования, интеграционное тестирование требует больше времени, и тестовые случаи должны выбираться на основе так называемой ценности для бизнеса. Методологии BDD (такие как Дано, Когда, Тогда друзья) могут дать вам некоторое представление о том, как делать мудрый выбор.

Устаревший поток. Есть шанс, что вы сможете пересмотреть свой код и решить кардинально его реорганизовать. Структура может быть изменена, название претерпевает серьезное переосмысление и т. д. Допустим, у вас есть некоторый модуль с именем A. Сначала переименуйте его в A_deprecated. В этот момент все должно работать как прежде, без сбоев. Обычно это вопрос переименования папки в IDE (если вы следовали правилу №1). Затем заново создайте модуль A с нуля. Реализуйте то, что вам нужно. Некоторые могут быть удалены, некоторые скопированы из A_deprecated. Затем выполните рефакторинг мест, где A_deprecated используется с A. Когда вы закончите процесс и ничто больше не зависит от A_deprecated, мы можем безопасно удалить его. Чем масштабнее рефакторинг, тем больше пользы дает этот подход. Потому что таким образом мы можем работать постепенно и параллельно с текущими основными исправлениями ошибок и так далее.