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

Одним из примеров этого является «Не повторяйся» (DRY). Это был один из первых принципов программирования, который я усвоил, и который не имел прямого отношения к запуску моей программы. Для многих людей это знакомство с концепцией «стиля» кода: как код, который достигает одного и того же результата, не обязательно имеет такое же качество. DRY короткий, резкий, простой для понимания, и его обоснование имеет смысл:

  • СУХОЙ код можно использовать повторно: вы экономите время, не переписывая его.
  • СУХОЙ код поддается рефакторингу: изменение специфики фрагмента повторно используемого кода автоматически применяется ко всем местам, где он используется.
  • DRY-код поддается модульному тестированию: неизбыточный код также означает неизбыточные модульные тесты (хотя они все равно должны быть включены в интеграцию).

В свое время, просматривая код, я видел, что DRY действует как своего рода замена для тонкой архитектуры или осмысленных вариантов дизайна. В некоторых сценариях это может на самом деле нанести ущерб качеству фрагмента кода из-за чрезмерной архитектуры простого решения. В любом случае, иногда трудно предложить идти против такого титана дизайна программного обеспечения, как DRY, особенно когда речь идет о критике в обзоре кода. Чтобы облегчить себе задачу, я оцениваю, действительно ли код должен быть СУХИМ, с помощью нескольких быстрых проверок:

Это имеет смысл?

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

Рассмотрим следующий фрагмент:

def update_user(params)
  User.update(params)
end

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

Есть ли у него простой набор входных данных?

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

Рассмотрим следующую сигнатуру метода:

def add_user(
  from_intro_flow = false, from_settings_page = false, by_other_user = false, params = {})
  ...
end

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

Это действительно одна и та же функция, или она просто похожа?

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

Рассмотрим следующий фрагмент:

def search(resource = 'users', name = '')
  Net::HTTP.get("https://www.example.com/#{resource}?name=#{name}")
end
# This is a valid call to the external API
search('users', 'Marc')
# This is also a valid call to the external API
search('articles', 'DRY')

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

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

Вывод

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