СУХОЙ — не повторяйся

Есть много важных правил, которым должен следовать хороший разработчик, одно из них — принцип DRY. Является ли это одним из многих так называемых принципов «чистого кода», которые разработчик должен знать, понимать и применять в своем коде. Это правило было введено Энди Хантом и Дэйвом Томасом в книге The Pragmatic Programmer.

По сути, это говорит о том, что как разработчик мы всегда должны стараться не иметь дублированного кода — это так просто. Чтобы быть еще более точным, мы также должны думать о дублировании не только с точки зрения «код/строка», но также (в основном) с точки зрения бизнеса.

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

  • информация/данные о пользователе
  • Новостная рассылка
  • если какой-либо продукт недоступен, пользователь может оставить свой адрес электронной почты, чтобы получить уведомление позже, когда продукт будет доступен
  • пригласи друга и получи скидку 5%
  • и т. д…

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

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

  • мы снижаем стоимость разработки — если появится новое требование к процессу валидации, мы предоставляем свое изменение только в одном месте.
  • давайте представим, что кто-то сообщил об ошибке в процессе проверки добавления электронной почты в информационный бюллетень. Мы это исправляем, но только в том месте — для этого и создавался тикет, верно? Теперь, две недели спустя, мы получаем сообщение об этой же ошибке — что происходит?! Мы это уже исправили… но не везде. Предоставление единого представления кода позволяет нам решить проблему только в одном месте, и оно будет применяться ко всем функциям, где оно используется.
  • мы тестируем его только один раз — мы тестируем нашу логику — метод/класс валидатора.
  • вероятность ошибиться гораздо меньше. Если мы не «копируем-вставляем» код, а решим написать наш код вручную в 10 разных местах, мы часто можем сделать простую ошибку, например, например. опечатка в регулярном выражении.
  • мы рефакторим код только один раз — возможно, мы решили включить в процесс проверки какую-то приятную особенность последних фреймворков, мы можем сделать это только в одном месте

Почему иногда у нас возникают проблемы с соблюдением этого принципа. Одна из причин — длинные методы. Мы все видели метод 500 строк, классы 2000 строк — мы все были там. Когда вы пишете новую фичу, похожую на уже написанную, вы говорите себе: «Оооо!, эти 15 строчек кода, что звездочка в строке 834 — мне они тоже нужны». И что мы делаем — копируем-вставляем этот код и добавляем его в нашу новую фичу, и делаем еще больший бардак. Мы не только не следуем принципу DRY, но и уж точно не следуем «правилу бойскаута», то есть: «Всегда оставляйте код лучше, чем вы его нашли».

Очень заманчиво сделать как показано выше — простой копипаст — не правда ли. Что мы должны были сделать в примере выше? Возможно, используйте «шаблон метода извлечения». Просто создайте новый метод, добавьте код, который дублируется в этом новом методе, и просто вызовите его из нашей новой функции и из старого места.

Но должны ли мы всегда следовать принципу DRY. Должны ли мы всегда проводить рефакторинг и использовать, например. «Извлечение шаблона метода». ИМХО, нет. Помните, будьте очень осторожны при использовании слов «всегда» и «никогда», они очень опасны.

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

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

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

Некоторые дубли хороши, некоторые даже обязательны в приложении. Давайте немного вернемся к системе, о которой я упоминал ранее, а именно к книжному интернет-магазину. Мы работали с проверкой электронной почты пользователя. Достаточно ли проверить это письмо только на стороне клиента? В этом случае необходимо обеспечить валидацию как для клиентского сайта (формы), так и для подкрепленной стороны. Дублируем логику, а надо, вот так просто.

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

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

Теперь, что происходит в следующем году, когда приложение вырастет. У нас появились некоторые новые требования, и теперь наши три очень похожих класса начинают вести себя немного по-разному. Итак, что происходит сейчас… мы добавляем операторы instanceof в родительский класс, чтобы сказать: сделайте это для этого дочернего элемента, а это для этого дочернего элемента. Пожалуйста, помните: мы используем наследование не для добавления общей части перед скобками (если говорить в математической терминологии), а для поддержки полиморфизма, а полиморфизм — это различное поведение для разных случаев. Я постараюсь осветить опасность наследования в будущих постах, но, пожалуйста, не делайте этого.

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

  • вместо ручного тестирования, которое повторяется каждый раз, когда реализуется новая функция, вы должны подумать о предоставлении модульного теста, может быть, селенового теста, интеграционного теста.
  • вместо того, чтобы вручную загружать войну на сервер, вы должны подумать о CI, например, jenkins
  • и т. д…

Я надеюсь, что после прочтения этой статьи вы дважды подумаете, прежде чем копировать-вставлять какие-то операторы if из уже существующего кода.

Первоначально опубликовано на https://developersmill.com 26 апреля 2022 г.