Почему некоторые принципы программирования важны для понимания, но бесполезны на практике
Многие разработчики считают принципы программирования обязательными и используют их по дефолту во всех проектах. На самом деле большинство из них нереализуемы на практике — докажем это на нескольких примерах.
Эта статья — адаптированная расшифровка доклада о принципах программирования, прочитанного CEO Хекслета Кириллом Мокевниным на конференции Trampoline Meetup летом 2021 года.
Если попытаться вспомнить самые распространенные принципы программирования и подходы к разработке, то в списке наверняка окажутся CodeStyle, SOLID, DRY, KISS, YAGNI, CQS, LoD. И это далеко не исчерпывающий перечень.
Хотя принципов программирования множество, руководствоваться ими часто совершенно необязательно. К одним нужно относиться критически, другие нужно правильно использовать и только в частных случаях.
Моя задача — не рассказать, что правильно, а что нет, а посеять зерно сомнений в принципах, которые программисты воспринимают как прямое руководство к действию.
Небольшое отступление: я занимаюсь разработкой с 2007 года, писал код на разных языках и в последнее время отношусь к разным подходам к разработке достаточно спокойно. В Хекслете мы закладываем в студентов базу, на основе которой они могут принимать логические решения. Хорошая база — не то же самое, что хорошая математическая подготовка: это понимание базовых вещей и способность делать на их основе логически верные умозаключения.
При обучении студентов мы не сосредотачиваемся на конкретных подходах: часто они скорее вредят начинающему разработчику, чем помогают. Например, чем популярнее тот или иной принцип, тем больше разработчиков используют его по дефолту, а не потому, что считают его действительно полезным.
Рассмотрим несколько примеров.
DRY
Принцип Don’t Repeat Yourself (Не повторяйтесь) может хорошо работать в коде, но в тестах его применять нет никакого смысла. Если убрать дублирование кода, остаются только сложные абстракции. Если в тестах появляются сложные абстракции, они сами превращаются в код, который сложно понимать и нужно тестировать.
SOLID
Аббревиатура, предложенная Робертом Мартином. Разберем несколько букв из нее:
S или Single-responsibility principle (Принцип единственной ответственности) применяется для проектирования объектов, классов и методов. Его суть в том, что каждый из перечисленных элементов должен отвечать только за что-то одно.
Это очень абстрактный принцип, который сформулирован в духе «хорошо делай — хорошо будет». Несколько раз я проводил эксперимент: просил двух разработчиков ответить, реализован ли принцип в отдельно взятом классе. Оба всегда отвечают нет и называют совершенно разные причины.
React-приложения часто состоят только из одного класса. Это прямо противоречит принципу, но на практике не означает, что приложение реализовано неправильно.
L или Liskov substitution principle (Принцип подстановки Лисков) в интерпретации Мартина касается классов, но Барбара Лисков в оригинале сформулировала его для типов. На самом деле суть принципа в наследовании интерфейсов, чего на практике почти никто не делает — это происходит крайне редко и только в библиотеках.
Как и S, L — важный и правильный, но бесполезный на практике принцип.
LoD
Law of Demeter или закон Деметры говорит нам примерно следующее: «не разговаривай с незнакомцами и не создавайте ненужных зависимостей». Если рассматривать с точки зрения этого принципа код u.getСompany.getOwner().get …, то он будет написан неверно: в нем есть зависимости, которые могут привести к проблемам. В данном случае принципу можно последовать и переписать код, а можно оставить все как есть. Закон Деметры — это, скорее, рекомендация, о которой стоит помнить.
CQS
CQS или сommand-query separation (Разделение команд и запросов) — принцип разделения запросов (query) и команд на обработку (например сохранение данных или удаление), каждый из которых либо читает данные, либо обновляет их. Метод может быть запросом, возвращающим какое-то значение, или командой, но не одновременно тем и другим.
Например, если на Rails вы видите, что есть user.valid?, но нет if, в большинстве случаев такая ситуация связана с колбеком. Это могут быть данные, получаемые из формы, которые нужно рассортировать по категориям. valid отвечает за то, чтобы заполнить некую логику после валидации. Это ненормально и здесь помогает принцип CQS. В данном случае предикат меняет внутреннее состояние, но он не должен этого делать.
На одном из собеседований в Хекслет Rails-программист получила задачу написать генератор форм. В условии были классы с объектами для каждого метода и сам метод to string. Программистка реализовала генератор, использовав внутри to_string()
опцию options.delete(:class)
.
Если использовать объект второй раз, он отдаст другой вывод, поскольку delete
— мутирующий метод. Это недопустимое программирование, и эта опция была у программистки во всех методах.
В данном случае есть объект, переиспользовать который невозможно — иначе он обрушится. Этот пример наглядно демонстрирует, что в вебе из-за очень короткого жизненного цикла конструкций прощаются многие ошибки. Из-за этого программисты делают неправильные выводы о том, как работает тот или иной принцип. Это пример показывает, что CQS, в отличие от других описанных выше приниципов, можно использовать почти всегда — он не нужен только в исключительных ситуациях.
Заключение
Мартин Фаулер в одной из лучших книг по прикладному программированию «Шаблоны корпоративных приложений» описывает принцип распределения объектов так: «не распределяйте объекты». С практической точки зрения это очень понятно и вполне применимо.
Большинство принципов, описанных выше, не имеют ничего общего с реальной жизнью: чаще они создают избыточную сложность, чем делают код понятнее, безопаснее или правильнее. Поэтому подходить к их применению нужно критически.