[Перевод] DRY и цена неправильных абстракций
Эта статья давно висела у меня в списке задач. Но кажется, только сегодня у меня появились силы и время, чтобы материализовать её. Совпадение или нет, но я в том же кафе, где опубликовал недавно свою первую статью. Наверное, в напитки, которые тут подают, что-то подмешивают…
Так что? Бородатый, хороший совет — следовать лучшим практикам? Мы постоянно слышим о них. Мы даже дали им краткие прозвища, типа DRY или KISS, и используем на автомате в технических разговорах. Мы фанатично следуем концепции, и если кто-то случайно захочет или просто по незнанию не станет их соблюдать, мы выливаем на них вёдра грязной критики. Мы пленники этих убеждений и отказываемся отвернуться от них в нужный момент.
Конечно, я не намекаю, что такие принципы, как DRY — плохие. Это определенно не так. Просто я считаю, что всё зависит от ситуации. Сильно. Что касается именно DRY, это ведёт к логическому выводу: «На самом деле я тот, кто иногда склоняет других к дублированию, а не абстракции».
Да, вы правильно прочитали. Дублированный код (он же копипаст) может быть полезной практикой. Особенно когда абстракция, которая заменяет повторяющиеся части кода, причиняет боль при попытках разобраться в ней.
Как распределяется время программиста
Когда я говорю людям, чем я зарабатываю на жизнь, им всем кажется, что я какой-то странный, что я луплю по клавиатуре по десять часов в день или больше.
Хоть я сам и не уверен, что со мной всё в порядке, я уверен, что не пишу код по десять часов подряд. Суть в том, что мы, как программисты, тратим куда больше времени на чтение кода, а не его написание. Я не уверен, что вы когда-то встречали подобную информацию, но исследования и Роберт C. Мартин утверждают, что для такого вида деятельности существует какая-то пропорция. И я, например, вижу поразительную разницу. Оказывается, на каждый час написания кода, приходится десять часов чтения кода (своего или чьего-то другого).
Это крайне важно. Усилия, которые мы вкладываем за день работы уходят в основном на чтение. Конечно, чтения кода не достаточно. Нужно ещё его понимать. А это значит, что мы должны делать все возможное, чтобы создавать ясный, краткий и легко читаемый код. Это выгодно каждому, включая нас самих в будущем. Имейте это в виду, потому что мы вернемся к этой идее позже.
Заметка про DRY
Для тех, кто не знаком с термином DRY, он означает — не повторяться (Don’t Repeat Yourself). Этот принцип программирования или, если хотите — лучший приём, выступает за создание абстракций поверх каждой повторяющейся части кода.
У DRY есть масса преимуществ. Первое — абстракции говорят о том, что, если в будущем потребуются изменения, вам придется справляться с ними в одном месте — в самой абстракции.
Поглощая функциональность другого модуля, чей-то API и т.д. вы заботитесь только о том, как выглядит интерфейс (или абстракция). Вас не беспокоит базовая реализация. Поэтому шаблоны программного дизайна, вроде шаблона интерфейса, позволяют легко выполнять рефакторинг реализации, не мешая абстракции, которой пользуются другие.
Так что абстракции — это хорошо, и DRY полностью оправдывает себя. Тогда почему же я настаиваю на копировании кода в некоторых сценариях?
Ну, просто потому что…
Цена абстракций
За каждую абстракцию нужно платить. Затраты не всегда можно оценить сразу. Но со временем они всплывают.
Абстракции содержат дополнительный уровень метаданных. Метаданные, существование которых в принципе может быть вам не понятно (особенно, если не вы их писали). Каждая новая порция информации усиливает когнитивную нагрузку на мозг. И, как результат, время, которое вы тратите на чтение такого куска кода, увеличивается.
Глубокая кроличья нора
Проблема с DRY и фанатичным поклонением этому принципу не заметна в небольших проектах. Но заметна в средних и крупных.
В таких проектах большая редкость, если на одну копию будет приходиться только одна абстракция. Потому что, по мере развития проекта и появления новых требований, старый код всегда нуждается в коррекции.
Представьте такой сценарий. Вы присоединяетесь к новому проекту и просматриваете код в первый раз. После того, как вы привыкаете к тому, как он построен и понимаете, как всё работает, вы начинаете реализовать новые опции, изменять старые и прочее. Вы изменяете существующий код и абстракции. Вы всё это не писали, но оно там. И у этого, скорее всего, есть очень веская причина.
Как сказала Санди Мец:
Уже написанный код имеет мощный авторитет. Само его существование свидетельствует о его правильности и нужности.
Он заставляет вас не хотеть прикасаться к нему.
Теперь новая фича определенно может использовать ту хорошую абстракцию, которая уже существует. Но, как выясняется, абстракция нуждается в небольшой доработке. Она не была рассчитана на этот конкретный пользовательский сценарий. Если бы только вы могли чуток её поменять… Или, может, написать поверх существующей новую абстракцию, которая инкапсулирует дополнительную повторяющуюся логику, м? Да, кажется это верное решение! Вот что подразумевает DRY…
Прекрасно видно, как мы умеем доводить это безумие до абсолютной крайности. Крайности, где всё инкапсулировано в абстракции. А те, в свою очередь, завёрнуты в более сложные абстракции. И так до бесконечности…
В таких случаях абстракция теряет свою ценность. Она существует из-за убеждений, которым мы слепо следуем. И это делает абстракцию некорректной. Она существует только потому, что у нас есть возможность её создать.
Как уже упоминалось, абстракции оказывают когнитивную нагрузку на мозг. Почти всегда мы расплачиваемся препятствиями и временем, необходимым на расшифровку (помните, мы проводим больше времени на чтение кода, чем на написание). Но некорректная абстракция еще хуже, чем просто абстракция.
Потому что некорректные абстракции не только бьют вас по яйцам, они ещё в это время загибаются от смеха.
DRYить или не DRYить
Так когда же инкапсулировать повторяющийся код, а когда нет?
Ответ по сути простой. Но с практической точки зрения не совсем верный. Но это тоже приходит с опытом.
Абстрагируйте всегда, если абстракция не обернётся вам дороже дублированного кода.
То есть, если другой участник проекта тратит часы в попытках понять абстракцию, которую вы написали, то вы, вероятно, делаете что-то не так. Не создавайте абстракции только потому, что у вас есть возможность это делать. Прогнозируйте, будет ли ещё встречаться в этой конкретной части дублированный код или нет, и принимайте соответствующее решение.
Иногда дублировать менее трудозатратно, чем следовать дереву вложенных вызовов различных методов, отслеживать передаваемые параметры и возможные побочные эффекты и т.д.
Заключительная мысль в защиту себя
Надеюсь, эта статья не кричит «к чертям этот DRY и другое дерьмо!». Я безоговорочно считаю его хорошим принципом программирования. Но всё же, я призываю вас не следовать ему слепо. Рассматривайте всё, что узнаёте, в контексте и всегда ставьте под сомнение обоснованность своих идей и действий. Это единственный разумный путь к повышению профессионализма.
(Перевод Наталии Басс)
Комментарии (9)
26 сентября 2016 в 12:13
+3↑
↓
tl: dr все хорошо в меру.26 сентября 2016 в 12:58
0↑
↓
этим комментарием можно подписать любую статью на хабре. подозреваю, что не только на хабре.
26 сентября 2016 в 15:14 (комментарий был изменён)
0↑
↓
Непонятно, что, чем и в чём измеряли, но в целом, фраза хорошая, жизненная.
26 сентября 2016 в 12:19
+3↑
↓
в программировании нет проблем, которые нельзя решить новым уровнем абстракции, кроме проблемы излишнего количества абстракций)26 сентября 2016 в 13:18
0↑
↓
тут проблема не в лишнем количестве абстракций, а в неправильном выборе абстракций
26 сентября 2016 в 12:22
0↑
↓
DRY «не работает», потому что мы не умеем прогнозировать какие абстракции нам нужны в будущем.
Решение: прогнозировать, какие абстракции нам в будущем не нужны и отказываться от «лишних».
Что-то типа постановления московского бургомистра, согласно которому о пожаре надо было сообщать за три часа до возгорания.26 сентября 2016 в 13:33 (комментарий был изменён)
+1↑
↓
Основной посыл статьи: DRY использовать нехорошо, т.к. в процессе можно нечаянно породить кривую абстракцию.
Так ее и без DRY сочинить можно — было бы желание. Рецепт тут прост: не порождайте кривые абстракции, блин!ИМХО, если у разработчика достаточно квалификации, чтобы аргументировать, почему вот здесь дублирование кода лучше — вероятно, ему просто лень, ибо той же самой квалификации должно хватить на грамотное изменение без Copy-Paste существующего кода.
26 сентября 2016 в 14:13
0↑
↓
В конце статьи написана заключительная мысль в защиту себя, дабы превентивно опровергнуть обвинения подобные Вашим.
Основной посыл статьи такойже как в The Wrong Abstraction — Sandi Metz:
duplication is far cheaper than the wrong abstraction
26 сентября 2016 в 14:45
0↑
↓
Я не обвиняю, мне просто интересно, как подсчитать, что Copy-Paste дешевле, даже учитывая соотношение read/write кода как 10/1.С одной стороны, вы пишите новый код, который так же надо будет часто читать. Т.е. увеличиваете трудозатраты.
С другой стороны, вы пишите дублирующийся код, который, скорее всего, нужно в будущем синхронизировать, а это значит: а) вы увеличиваете трудозатраты, б) когда-то эти два участка кода фатально рассинхронизируются, и понимать, и, тем более, рефакторить, станет сложнее, т.е. снова растут затраты.
Далее, если согласиться с принципом Single Responsibility, значит признать, что уже в исходном коде есть проблемы, поскольку функциональную часть одной сущности потребовалось продублировать в другой. Следовательно, бояться появления кривых абстракций поздно — они уже есть — надо их признавать и побеждать.