Эта статья давно висела у меня в списке задач. Но кажется, только сегодня у меня появились силы и время, чтобы материализовать её. Совпадение или нет, но я в том же кафе, где опубликовал недавно свою первую статью. Наверное, в напитки, которые тут подают, что-то подмешивают...
Так что? Бородатый, хороший совет — следовать лучшим практикам? Мы постоянно слышим о них. Мы даже дали им краткие прозвища, типа DRY или KISS, и используем на автомате в технических разговорах. Мы фанатично следуем концепции, и если кто-то случайно захочет или просто по незнанию не станет их соблюдать, мы выливаем на них вёдра грязной критики. Мы пленники этих убеждений и отказываемся отвернуться от них в нужный момент.
Конечно, я не намекаю, что такие принципы, как DRY — плохие. Это определенно не так. Просто я считаю, что всё зависит от ситуации. Сильно. Что касается именно DRY, это ведёт к логическому выводу: «На самом деле я тот, кто иногда склоняет других к дублированию, а не абстракции».
Да, вы правильно прочитали. Дублированный код (он же копипаст) может быть полезной практикой. Особенно когда абстракция, которая заменяет повторяющиеся части кода, причиняет боль при попытках разобраться в ней.
Как распределяется время программиста
Когда я говорю людям, чем я зарабатываю на жизнь, им всем кажется, что я какой-то странный, что я луплю по клавиатуре по десять часов в день или больше.
Хоть я сам и не уверен, что со мной всё в порядке, я уверен, что не пишу код по десять часов подряд. Суть в том, что мы, как программисты, тратим куда больше времени на чтение кода, а не его написание. Я не уверен, что вы когда-то встречали подобную информацию, но исследования и Роберт C. Мартин утверждают, что для такого вида деятельности существует какая-то пропорция. И я, например, вижу поразительную разницу. Оказывается, на каждый час написания кода, приходится десять часов чтения кода (своего или чьего-то другого).
Это крайне важно. Усилия, которые мы вкладываем за день работы уходят в основном на чтение. Конечно, чтения кода не достаточно. Нужно ещё его понимать. А это значит, что мы должны делать все возможное, чтобы создавать ясный, краткий и легко читаемый код. Это выгодно каждому, включая нас самих в будущем. Имейте это в виду, потому что мы вернемся к этой идее позже.
Заметка про DRY
Для тех, кто не знаком с термином DRY, он означает — не повторяться (Don't Repeat Yourself). Этот принцип программирования или, если хотите — лучший приём, выступает за создание абстракций поверх каждой повторяющейся части кода.
У DRY есть масса преимуществ. Первое — абстракции говорят о том, что, если в будущем потребуются изменения, вам придется справляться с ними в одном месте — в самой абстракции.
Поглощая функциональность другого модуля, чей-то API и т.д. вы заботитесь только о том, как выглядит интерфейс (или абстракция). Вас не беспокоит базовая реализация. Поэтому шаблоны программного дизайна, вроде шаблона интерфейса, позволяют легко выполнять рефакторинг реализации, не мешая абстракции, которой пользуются другие.
Так что абстракции — это хорошо, и DRY полностью оправдывает себя. Тогда почему же я настаиваю на копировании кода в некоторых сценариях?
Ну, просто потому что...
Цена абстракций
За каждую абстракцию нужно платить. Затраты не всегда можно оценить сразу. Но со временем они всплывают.
Абстракции содержат дополнительный уровень метаданных. Метаданные, существование которых в принципе может быть вам не понятно (особенно, если не вы их писали). Каждая новая порция информации усиливает когнитивную нагрузку на мозг. И, как результат, время, которое вы тратите на чтение такого куска кода, увеличивается.
Глубокая кроличья нора
Проблема с DRY и фанатичным поклонением этому принципу не заметна в небольших проектах. Но заметна в средних и крупных.
В таких проектах большая редкость, если на одну копию будет приходиться только одна абстракция. Потому что, по мере развития проекта и появления новых требований, старый код всегда нуждается в коррекции.
Представьте такой сценарий. Вы присоединяетесь к новому проекту и просматриваете код в первый раз. После того, как вы привыкаете к тому, как он построен и понимаете, как всё работает, вы начинаете реализовать новые опции, изменять старые и прочее. Вы изменяете существующий код и абстракции. Вы всё это не писали, но оно там. И у этого, скорее всего, есть очень веская причина.
Как сказала Санди Мец:
Уже написанный код имеет мощный авторитет. Само его существование свидетельствует о его правильности и нужности.
Он заставляет вас не хотеть прикасаться к нему.
Теперь новая фича определенно может использовать ту хорошую абстракцию, которая уже существует. Но, как выясняется, абстракция нуждается в небольшой доработке. Она не была рассчитана на этот конкретный пользовательский сценарий. Если бы только вы могли чуток её поменять… Или, может, написать поверх существующей новую абстракцию, которая инкапсулирует дополнительную повторяющуюся логику, м? Да, кажется это верное решение! Вот что подразумевает DRY...
Прекрасно видно, как мы умеем доводить это безумие до абсолютной крайности. Крайности, где всё инкапсулировано в абстракции. А те, в свою очередь, завёрнуты в более сложные абстракции. И так до бесконечности...
В таких случаях абстракция теряет свою ценность. Она существует из-за убеждений, которым мы слепо следуем. И это делает абстракцию некорректной. Она существует только потому, что у нас есть возможность её создать.
Как уже упоминалось, абстракции оказывают когнитивную нагрузку на мозг. Почти всегда мы расплачиваемся препятствиями и временем, необходимым на расшифровку (помните, мы проводим больше времени на чтение кода, чем на написание). Но некорректная абстракция еще хуже, чем просто абстракция.
Потому что некорректные абстракции не только бьют вас по яйцам, они ещё в это время загибаются от смеха.
DRYить или не DRYить
Так когда же инкапсулировать повторяющийся код, а когда нет?
Ответ по сути простой. Но с практической точки зрения не совсем верный. Но это тоже приходит с опытом.
Абстрагируйте всегда, если абстракция не обернётся вам дороже дублированного кода.
То есть, если другой участник проекта тратит часы в попытках понять абстракцию, которую вы написали, то вы, вероятно, делаете что-то не так. Не создавайте абстракции только потому, что у вас есть возможность это делать. Прогнозируйте, будет ли ещё встречаться в этой конкретной части дублированный код или нет, и принимайте соответствующее решение.
Иногда дублировать менее трудозатратно, чем следовать дереву вложенных вызовов различных методов, отслеживать передаваемые параметры и возможные побочные эффекты и т.д.
Заключительная мысль в защиту себя
Надеюсь, эта статья не кричит "к чертям этот DRY и другое дерьмо!". Я безоговорочно считаю его хорошим принципом программирования. Но всё же, я призываю вас не следовать ему слепо. Рассматривайте всё, что узнаёте, в контексте и всегда ставьте под сомнение обоснованность своих идей и действий. Это единственный разумный путь к повышению профессионализма.
(Перевод Наталии Басс)