Pull to refresh

Comments 21

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

этот комментарий подходит к любому комментарию :)
UFO just landed and posted this here

тут проблема не в лишнем количестве абстракций, а в неправильном выборе абстракций

DRY "не работает", потому что мы не умеем прогнозировать какие абстракции нам нужны в будущем.
Решение: прогнозировать, какие абстракции нам в будущем не нужны и отказываться от "лишних".
Что-то типа постановления московского бургомистра, согласно которому о пожаре надо было сообщать за три часа до возгорания.

Основной посыл статьи: DRY использовать нехорошо, т.к. в процессе можно нечаянно породить кривую абстракцию.
Так ее и без DRY сочинить можно — было бы желание. Рецепт тут прост: не порождайте кривые абстракции, блин!

ИМХО, если у разработчика достаточно квалификации, чтобы аргументировать, почему вот здесь дублирование кода лучше — вероятно, ему просто лень, ибо той же самой квалификации должно хватить на грамотное изменение без Copy-Paste существующего кода.

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


Основной посыл статьи такойже как в The Wrong Abstraction — Sandi Metz:


duplication is far cheaper than the wrong abstraction
Я не обвиняю, мне просто интересно, как подсчитать, что Copy-Paste дешевле, даже учитывая соотношение read/write кода как 10/1.

С одной стороны, вы пишите новый код, который так же надо будет часто читать. Т.е. увеличиваете трудозатраты.

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

Далее, если согласиться с принципом Single Responsibility, значит признать, что уже в исходном коде есть проблемы, поскольку функциональную часть одной сущности потребовалось продублировать в другой. Следовательно, бояться появления кривых абстракций поздно — они уже есть — надо их признавать и побеждать.

В моём понимании, тут нужно двигаться от понятия "неверная абстракция". Как правило, с большей или меньшей вероятностью, можно предположить как будет развиваться код: будет он расходится или будет изменятся синхронно. В первом случае, как нетрудно догадаться, делать абстракцию будет излишним, во втором, напротив, необходимо её сделать.


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


Объясняется это тем, что ввести абстракцию будет дешевле, чем убрать ту, которая не заработала.


По крайней мере, я понял текст таким образом, и это, более или менее, согласуется с моим опытом.

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

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

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

Ну, опыт у всех разный, тут уж ничего не попишешь. Отсюда, собственно, и разнообразие подходов, и споры об их достоинствах.

UFO just landed and posted this here
Как раз недавно продублировал пару методов, чтобы во-первых не городить ещё один класс (в родительский их поместить было бы совсем неправильно), а во-вторых сами классы стали быстрее читаться. Они маленькие и методы в них короткие. Конечно из названий понятно что они делают, но не понятно, например, что они лезут в базу и не кешируют результат, а значит ответственность кешировать ли его (и как) лежит на пользовательском коде. Разумеется можно было бы инкапсулировать кеширование минимум двумя способами (по аргументу или создав обёртки/дочерние классы), но это во-первых ограничивало бы применение кое-каких оптимизаций, а во-вторых маскировало бы «цену обращения» и кто-нибудь обязательно бы начал использовать всё это неправильно, сильно повредив производительности, как уже бывало.
Это я к тому, что действительно нужно искать компромисс между множеством характеристик, нельзя делать из одной главную в ущерб остальным (например отсутствие дублирований превыше всего). А если где-то что-то было корректно тогда, когда оно было сделано, это не значит, что так будет всегда и не исключено, что вышеописанный пример когда-нибудь не потребует ввести эти абстракции и убрать дублирование. Но это же будет уже другая ситуация, возможность которой учтена и это не займёт много времени.

А разве нельзя писать код так, чтобы потраченное время и объём прочитанного кода был минимальным для принятия каких либо обоснованных решений?
Может этим, специфическими дизайном, отличаются какие то языки программирования, основанные на этих принципах, важных в коммерческом программировании где времени — всегда мало?

Увлекаясь DRY-ем можно достигнуть предела «совершенства» в виде классов с методами с кучей if или switch/case логики и безумным числом параметров, заполнение которых опционально.

Правильно было сказано, что всё хорошо в меру.

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

Многие опытные разработчики пытаются сделать «красиво» и это приводит к тому, что их код с трудом может понять даже другой senior. Если вы хотите проверить, хороший ли ваш код — покажите его Junior-у и посмотрите насколько быстро он в коде разберётся.
Увлекаясь DRY-ем можно достигнуть предела «совершенства» в виде классов с методами с кучей if или switch/case логики и безумным числом параметров, заполнение которых опционально.

Стратегию и замену условной логики полиморфизмом уже отменили?

Конечно нет. Это отличные решения для избавления от проблемы.
Но часто в реальной жизни новая логика дописывается к уже существующей и часто нет возможности провести глобальный рефакторинг для добавления новой абстракции или внедрение паттерна (той же Стратегии к примеру). Конечно в идеальном сценарии ничего рефакторить не прийдётся, т.к. этот случай должен быть предусмотрен архитектурой, но мир не идеален…
С другой стороны не будете же вы добавлять «Стратегию» на каждый создаваемый вами класс?!

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

Sign up to leave a comment.

Articles