Comments 21
DRY "не работает", потому что мы не умеем прогнозировать какие абстракции нам нужны в будущем.
Решение: прогнозировать, какие абстракции нам в будущем не нужны и отказываться от "лишних".
Что-то типа постановления московского бургомистра, согласно которому о пожаре надо было сообщать за три часа до возгорания.
Так ее и без DRY сочинить можно — было бы желание. Рецепт тут прост: не порождайте кривые абстракции, блин!
ИМХО, если у разработчика достаточно квалификации, чтобы аргументировать, почему вот здесь дублирование кода лучше — вероятно, ему просто лень, ибо той же самой квалификации должно хватить на грамотное изменение без Copy-Paste существующего кода.
В конце статьи написана заключительная мысль в защиту себя, дабы превентивно опровергнуть обвинения подобные Вашим.
Основной посыл статьи такойже как в The Wrong Abstraction — Sandi Metz:
duplication is far cheaper than the wrong abstraction
С одной стороны, вы пишите новый код, который так же надо будет часто читать. Т.е. увеличиваете трудозатраты.
С другой стороны, вы пишите дублирующийся код, который, скорее всего, нужно в будущем синхронизировать, а это значит: а) вы увеличиваете трудозатраты, б) когда-то эти два участка кода фатально рассинхронизируются, и понимать, и, тем более, рефакторить, станет сложнее, т.е. снова растут затраты.
Далее, если согласиться с принципом Single Responsibility, значит признать, что уже в исходном коде есть проблемы, поскольку функциональную часть одной сущности потребовалось продублировать в другой. Следовательно, бояться появления кривых абстракций поздно — они уже есть — надо их признавать и побеждать.
В моём понимании, тут нужно двигаться от понятия "неверная абстракция". Как правило, с большей или меньшей вероятностью, можно предположить как будет развиваться код: будет он расходится или будет изменятся синхронно. В первом случае, как нетрудно догадаться, делать абстракцию будет излишним, во втором, напротив, необходимо её сделать.
Вопрос, что делать, если сценарии равновероятны. Возможно, стоит предпочесть дублирование и вернуться к этому, когда появятся новые данные, способные повлиять на решение.
Объясняется это тем, что ввести абстракцию будет дешевле, чем убрать ту, которая не заработала.
По крайней мере, я понял текст таким образом, и это, более или менее, согласуется с моим опытом.
1. Изменят одну из копий, поэтому в момент возврата к коду мы рискуем его просто не узнать и потратить существенное время на рефакторинг, учитывающий различия. Не факт, что безбажный.
2. Завяжутся на задублированный код, поэтому рефакторинг уже будет затрагивать не только первоначальные 2 копии, но и другие 100500 новых мест. Опять же лишняя работа.
Это я к тому, что действительно нужно искать компромисс между множеством характеристик, нельзя делать из одной главную в ущерб остальным (например отсутствие дублирований превыше всего). А если где-то что-то было корректно тогда, когда оно было сделано, это не значит, что так будет всегда и не исключено, что вышеописанный пример когда-нибудь не потребует ввести эти абстракции и убрать дублирование. Но это же будет уже другая ситуация, возможность которой учтена и это не займёт много времени.
А разве нельзя писать код так, чтобы потраченное время и объём прочитанного кода был минимальным для принятия каких либо обоснованных решений?
Может этим, специфическими дизайном, отличаются какие то языки программирования, основанные на этих принципах, важных в коммерческом программировании где времени — всегда мало?
Правильно было сказано, что всё хорошо в меру.
Идеальный код, это код в котором легко можно найти и исправить любую ошибку. Это совокупность приципов KISS и DRY. Т.е. код должен быть написан максимально просто и прямолинейно и в то же время, чтобы исправление пришлось делать только в одном месте в коде.
Многие опытные разработчики пытаются сделать «красиво» и это приводит к тому, что их код с трудом может понять даже другой senior. Если вы хотите проверить, хороший ли ваш код — покажите его Junior-у и посмотрите насколько быстро он в коде разберётся.
Увлекаясь DRY-ем можно достигнуть предела «совершенства» в виде классов с методами с кучей if или switch/case логики и безумным числом параметров, заполнение которых опционально.
Стратегию и замену условной логики полиморфизмом уже отменили?
Но часто в реальной жизни новая логика дописывается к уже существующей и часто нет возможности провести глобальный рефакторинг для добавления новой абстракции или внедрение паттерна (той же Стратегии к примеру). Конечно в идеальном сценарии ничего рефакторить не прийдётся, т.к. этот случай должен быть предусмотрен архитектурой, но мир не идеален…
С другой стороны не будете же вы добавлять «Стратегию» на каждый создаваемый вами класс?!
DRY и цена неправильных абстракций