Дублирование дешевле неправильной абстракции

Разработчик A замечает дублирование кода, извлекает его в новую абстракцию и тут же понимает, что решил проблему. Разработчик B получает новое требование, которое почти подходит существующей абстракции. Чтобы сохранить её, B добавляет параметр и условную логику. Затем приходит требование C, потом D. Конечная абстракция обрастает условными ветками, становится непонятной и ломкой, каждое новое изменение рискует сломать весь механизм.

Вместо попытки спасти усложнившуюся абстракцию через укол новых параметров, Sandi Metz рекомендует развернуться назад: разворошить абстракцию обратно на дублирование, дать каждому вызывающему коду только нужные ему части. Это удаляет условия и возвращает ясность. Затем, глядя на полученное дублирование, можно увидеть, какие новые правильные абстракции действительно нужны. Сложность и сроки давления часто сбивают разработчиков инвестировать в спасение неправильной абстракции вместо того, чтобы её заменить.

Ключевые факты

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

Ред. Совет 2014 года, который индустрия двенадцать лет переоткрывает каждый раз, когда упирается в собственную «универсальную» функцию с восемью флагами.

Почему это важно

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

Ред. Парадокс: код, которого все боятся касаться, формально считается «переиспользуемым», то есть успешным. Метрика DRY молчит про то, во что обходится каждая новая ветка if.

Кому это важно

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

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

Как это применить

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

Ред. Разворошить абстракцию обратно звучит как откат назад, но на ревью это придётся защищать как прогресс. Готовьтесь объяснять, почему вы добровольно «развалили» общую функцию.

Можно ли доверять

Мец, автор нескольких книг по объектно-ориентированному дизайну и лектор RailsConf. Идею выдвинула уже в 2014 году, и за 12 лет она получила поддержку в сообществе. Статья предлагает конкретный, проверенный паттерн, а не умозрительное рассуждение. Сама рекомендация консервативна и безопасна.

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

Риски и подводные камни

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

Ред. Та же логика, которой оправдывали лишний параметр («почти подходит»), теперь оправдает лишнюю копипасту («слишком разные»). Маятник sunk cost легко перелетает в противоположную крайность.

«Дублирование дешевле неправильной абстракции»

— Sandi Metz, 'The Wrong Abstraction'