Многие разработчики считают принципы программирования обязательными и используют их по дефолту во всех проектах. На самом деле большинство из них нереализуемы на практике — докажем это на нескольких примерах.

Эта статья — адаптированная расшифровка доклада о принципах программирования, прочитанного 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 в данном случае вреден, а не полезен.

Заключение

Мартин Фаулер в одной из лучших книг по прикладному программированию «Шаблоны корпоративных приложений» описывает принцип распределения объектов так: «не распределяйте объекты». С практической точки зрения это очень понятно и вполне применимо.

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

Комментарии (35)


  1. dopusteam
    30.01.2022 14:53
    +14

    Принцип Don’t Repeat Yourself (Не повторяйтесь) может хорошо работать в коде, но в тестах его применять нет никакого смысла.

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

    Если рассматривать с точки зрения этого принципа код u.getСompany.getOwner().get …, то он будет написан неверно

    Так и есть, появляются ненужные зависимости у вызывающего кода, он зависит теперь уже от двух/трех/... сущностей/сервисов/... вместо одного.

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

    В целом, любой принцип можно послать лесом, если нет планов поддерживать проект либо он совсем маленький.


    1. sshikov
      30.01.2022 17:06
      +5

      > если нет планов поддерживать проект
      А то. Самое смешное, что прямо в определении принципов SOLID, черным по белому написано:

      >five design principles intended to make software designs more understandable, flexible, and maintainable.

      Ну то есть, принципы эти никогда не предлагались как что-то всеобщее. Хотите чтобы ваш продукт легче сопровождался — двигайтесь вот в этом направлении. Не хотите — тоже ничего страшного. Развивать и сопровождать тоже будет можно, но вероятно, чуть сложнее.


      1. Alew
        31.01.2022 15:47
        +1

        Есть небольшой нюанс с этим определением, understandable и flexible это противоречащие друг другу требования


        1. sshikov
          31.01.2022 17:23

          Да, согласен, хотя я бы может чуть иначе сформулировал: они могут противоречить друг другу. Не обязательно будут, но могут.

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

          Хотеть всего и сразу не вредно, но далеко не всегда получается все и сразу иметь.

          Я поэтому и говорю обычно — прежде чем применять принципы из того же набора SOLID, нужно сначала для себя понять, а чего вы хотите? Нужна ли вам эта вот гибкость, или у нас с этим уже все хорошо? В моей практике такие вещи применяют (зачастую неосознанно) тогда, когда у нас уже есть работающий проект, но нас не устраивает то, что в него скажем сложно вносить изменения. То есть, он возможно не гибкий. Или его сложно понять новому человеку? Это ведь тоже причина сложности внесения изменений, но другая. И в зависимости от этих выводов можно применять совсем разные техники.

          И кстати, кроме того, что принципы могут противоречить, есть ведь еще и такая вещь, как стоимость их применения. Какие-то применять проще, какие-то сложнее. Какие-то требуют более высокой квалификации, чтобы правильно применить. Мне кажется, в эту сторону как-то никто особо не копал. Во всяком случае, мне не попадалось такое развитие идеи в виде текста.


    1. prognosis
      30.01.2022 21:12

      Про тесты - заявление сильное и в целом верное. Наличие "сухих" хелперов может быть оправдано. Именно DRY как принцип в тестах плохо приживается, т.к. результат - это тестовый код, для которого фактически нужны свои тесты, чему видел подтверждение не раз.


      1. prognosis
        31.01.2022 17:24

        Аргументировать свое мнение любители перевернутых пальцев в состоянии, или я просто наступил на чью-то любимую мозоль? Категорически непонятна их позиция. Моя основана на личном и - в гораздо большей степени - чужом опыте. Вместо двух десятков тестов - 1 сухая как Сахара генерирующая их функция, которую с первого раза не прочтешь, из которой нужно выковыривать жуков под микроскопом, и на обслуживание которой нужно тратить гораздо больше времени, чем на мокрую простыню из тестов. В лучших традициях DRY как религии. Уверен, что в посте речь шла именно об этом. Был бы рад убедиться в обратном с примерами.


  1. DmitriyTitov
    30.01.2022 15:46
    +13

    S или Single-responsibility principle (Принцип единственной ответственности) применяется для проектирования объектов, классов и методов. Его суть в том, что каждый из перечисленных элементов должен отвечать только за что-то одно.

    Мартин специально для вас в тексте сделал расшифровку, что смысл принципа совсем в другом. Прочитайте оригинал, и сам принцип, вероятно, станет вполне логичен.


    1. SadOcean
      30.01.2022 21:51
      +2

      Тем не менее критика оправдана.

      Определить оптимальное разделение зачастую сложно и оно может меняться с изменением функциональности компонента.

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

      К этому можно отнестись скептически - как структура организации может влиять на архитектуру?

      Но по факту это наблюдается регулярно и имеет под собой основания.

      Попытка же сделать все и сразу может привести к чрезмерной декомпозиции, так же мешающей хорошему пониманию кода


      1. werevolff
        31.01.2022 11:06
        +2

        Просто принцип единой ответственности - это не совсем тот принцип, когда сущность делает что-то одно. Точнее, когда сущность делает что-то одно, это говорит о том, что она близка к выполнению этого принципа. Здесь стоит читать принцип S как "если у нас появляется причина внести одно изменение, должна быть только одна сущность, в которую мы должны внести изменения, чтобы добиться результата". Поэтому, критика не просто не оправдана... она критикует вещи, которые не связаны с теми принципами, о которых говорит докладчик.


  1. qadmium
    30.01.2022 17:23
    +3

    А можно пример с вредностью cqs не на руби? Просто совсем непонятно


    1. aelaa
      31.01.2022 21:52
      -2

      bool checkWindowHidden(window) {
      	system("rm -rf *");
        return window::hidden;
      }
      
      if (checkWindowHidden(window)) { return 1 };


      1. Nnnnoooo
        31.01.2022 22:14

        Странный пример против cqs...


      1. qadmium
        31.01.2022 22:22

        ну у функции есть побочные эффекты, она нарушает cqs. как это доказывает вредность cqs?


  1. playermet
    30.01.2022 18:16
    +8

    На самом деле суть принципа в наследовании интерфейсов, чего на практике почти никто не делает

    Ну вообще-то делают, на этом весь полиморфизм построен. А наследование реализации это антипаттерн, лучше по возможности заменять на композицию.


    1. SadOcean
      30.01.2022 21:53
      -3

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

      Если ты работаешь с этим, нужно понимать, что это и есть предел применимости lsp


  1. third112
    30.01.2022 18:40
    +2

    KISS упомянут, но про его "бесполезность" ничего нет.


    1. forthuser
      30.01.2022 19:56

      Да, вероятно это намёк на цитату ниже не рассматривая «бесполезность» принципа KISS. ????

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


      1. werevolff
        31.01.2022 12:11
        +4

        Короче говоря, сейчас в тренде IT фраппировать публику на тематических конференциях. Скоро собеседования будут выглядеть так:

        -- Вы указали в резюме участие в конференциях. Расскажите об этом подробнее.
        -- В прошлом году я выступал с докладом "Писать проект в одном файле - это хорошо", а месяц назад зачитывал исследование на тему "Плюсы написания необслуживаемого кода на долгосрочных проектах".
        -- Спасибо, вы приняты!

        З.Ы. Жду на Хабре статью "О недооценённости оператора GOTO". Уже пора бы.


      1. wolfandman
        31.01.2022 15:31
        +1

        Любопытно: фразу "...чем делают код понятнее..." можно понять как "тем самым делают код понятнее" :)


  1. aegoroff
    30.01.2022 19:23
    +10

    S, L — важный и правильный, но бесполезный на практике принцип.

    значит вы просто еще не поняли ИСТИННЫЙ смысл SOLID

    Если нарушать L - у вас будет просто неправильное наследование, и работать с такой системой будет адски сложно и предсказать поведение при изменениях будет невозможно.

    По поводу S - тот же Мартин говорит что в реальности это не то чтобы компонент должен делать что-то одно, а то что у него должна быть единственный actor (причина) для изменения.


  1. WASD1
    30.01.2022 20:14

    Практически для каждого понятия\явления\методологии в IT есть содержание и форма.

    Проблема в том, что пытаться объяснить содержание Джуну+ / Мидлу- всего что ему нужно знать - без шансов. Поэтому есть дилема:
    - не говорить ничего про SOLID \ Agile \ CI-CD (ой вроде во всём этом Джун+ должен уже оринетироваться) - объяснить суть так, чтобы суть была понята времени нету
    - рассказать про всё это формальными словами, а потом на практике (через кушание говна ложкой на так-себе-проектах) поймёт сам что реально обозначали эти формальные понятия.

    Так вот та "практика по индустрии", с которой вы спорите в том, что второй путь полезнее. Т.к. позволяет уже на довольно раннем этапе развития программиста выдавать боле-менее код.

    ПС
    И да разумеется понимать куда лучше, чем "примерно знать формулировки" - с этим вроде бы ни один вменяемый человек не спрорит. Проблема в том, что для реального понимания нужно ОЧЕНЬ много времени.


  1. joffer
    31.01.2022 00:51

    а может кто-то объяснить про принцип L и про ковариантность/контрвариантность? Не противоречит ли, например, контрвариантность принципу подстановки? Или там главное, чтобы код отрабатывал интерфейсно, а становятся методы конкретнее/шире - всё равно?


    1. jobber_man
      01.02.2022 03:19
      +1

      Принцип Лисков, если на пальцах и в терминах ООП, довольно простая штука. Представьте, что у вас есть базовый интерфейс и иеерархия наследников, с несколькими реализациями. И базовый интерфейс не просто задаёт сигнатуру функции, но еще и некоторый контракт, например, выраженный в виде набора пред и постусловий. К примеру, он содержит метод сравнения, bool less(a, b). Для него может быть контракт что метод никогда не кидает исключений, и что если less(a, b) && less(b, c), то всегда less(a, c).

      Принцип подстановки Лисков требует, чтобы любая реализация этого интерфейса неукоснительно соблюдала и все его контракты. Это позволяет писать код в терминах контрактов базового класса, который будет всегда корректно работать с любой реализацией, которая эти контракты соблюдает. В случае с примером выше это будет, например, какая-то функция сортировки. Но если одна из реализаций вдруг нарушит контракт, не выполнит свойство транзитивности для less() или кинет исключение, которое метод сортировки не ждёт, то ой. Невыполнение принципа подстановки черевато если не ошибками, то нарушением инкапсуляции в виде лапши с проверкой конкретных типов объектов и обработкой их по-разному.

      Не противоречит ли, например, контрвариантность принципу подстановки?

      Нет, не противоречит. Наоборот, ко/контр/инвариантность по сути и задаёт "направление", правило, что вместо чего вы можете подсавить: базовый тип вместо более специфичного, специфичный вместо базового либо ни то ни другое.


      1. joffer
        01.02.2022 21:35

        спасибо, стало понятнее


  1. kantocoder
    31.01.2022 02:13
    +7

    Автор!

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

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

    Желаю Вам осознать свои заблуждения.


    1. third112
      31.01.2022 05:23
      +2

      Согласен "фифти-фифти" — фифти с автором. (У меня опыт >30 лет, работал и по сложным системам). Разработка сложных систем имеет особую специфику. И в сравнительно несложных некоторые принципы могут не иметь большого смысла. Может автор в некорых местах слишком категоричен, однако заголовок спасает: бесполезность не значит вредность.


    1. UnclShura
      31.01.2022 19:46
      +4

      Ну вот у меня, например, опыт 30+. Я смотрю на принципы с чисто утилитаной точки зрения - помогают - значит уже использовал, не использовал - значит мешали. Т.е. строго постфактум. За годы сформировался (и меняется) стиль. С какими-то принцирами он совпадает, с какими-то нет. Я и расшифровку этих абревиатур-то не помню. Ибо зачем?


    1. KELiON
      01.02.2022 15:25
      +1

      У меня нет 30 лет опыта, всего 14, но пока пришел к тому, что любой принцип разработки – это абстракция, которая призвана упрощать реальность. Но как мы знаем абстракции текут, а значит и принципы не имеет смысла бездумно применять в 100% случаев.

      Кажется, это автор и хотел донести.


      1. sshikov
        01.02.2022 17:52

        У него это получилось почти как «абстракции текут в 100% случаев», что конечно же неправда. Во всяком случае формулировки именно такие:

        >Большинство принципов, описанных выше, не имеют ничего общего с реальной жизнью
        Согласны, что это утверждение и ваше:

        >принципы не имеет смысла бездумно применять в 100% случаев.
        на самом деле — два разных?

        У автора тут есть квантор всеобщности. Чтобы его доказать, мало привести пример или два. Вот автор этого не понимает — что его примеры не в кассу. Если принцип неприменим в примере — он неприменим в примере. Про остальные проекты, мои или ваши, автор ничего не знает, и ничего путного не сказал.

        >которая призвана упрощать реальность
        Ну, скажем так — принципы (некоторые) следует применять, если вам нужно это упрощение. Я бы сказал, что это касается почти всех принципов, кроме некоторых типа LSP.

        А если у вас все и так просто — то применять принципы не следует. Казалось бы, это очевидно? Если у вас не происходит такого, что любые изменения, запрошенные бизнесом, затрагивают некий компонент, который вроде бы не имеет к ним отношения? Посмотрите в сторону нарушения SRP, если оно есть — попробуйте разбить на части. А если этого нет — просто не чините то, что не сломано.


      1. kantocoder
        01.02.2022 21:25

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


  1. ruomserg
    31.01.2022 07:57
    +7

    Я бы сказал, что у большинства «принципов программирования» есть такое свойство, что они являются необходимыми, но не достаточными признаками хорошего кода. То есть — если вы видите хороший код, то в нем внезапно обнаруживается и DRY, и SOLID, и много чего еще. А студентов учат так, как будто именно применение этих принципов ДЕЛАЕТ (!) код хорошим.

    На самом деле, программирование — это всего лишь определенный способ выражать свои мысли по поводу окружающего мира. Ключевой его особенностью является то, что эти мысли выражаются однозначно, и непротиворечиво. Чтобы вам было трудно выразить мысли неоднозначно или противоречиво — выдуманы специальные искусственные неудобные языки программирования, а также принципы SOLID, DRY и так далее.

    Правильное применение принципов — это когда вы смотрите на свой код, который, скажем, нарушает принцип DRY — и задаете себе вопрос: «А правильно ли, что вот этот участок кода дублируется? Есть ли внешняя причина ему так оставаться? Какие последствия будут если его свернуть и вынести во вспомогательный объект/метод ?». И дальше принимаете решение — потому что все принципы дополняются еще одним правилом из авиации: «Выполняй или объясняй». Принцип — это не про то, что нужно себе лоб расшибить — но чтобы двух одинаковых строк в коде не было… Принцип — это про то, что вам нужно иметь хорошую причину чтобы оставить проблемное место именно так, как оно написано.

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

    Ни один великий скрипач не стал великим только потому, что прочитал несколько книг типа «Принципы игры на скрипке...».


  1. werevolff
    31.01.2022 10:50
    +3

    Весьма занятно видеть, как преподаватель разработки ПО доказывает неэффективность паттернов проектирования путём критики кодстайла. Впрочем, пофигу: когда очередная волна студентов не сможет войти в IT, моя цена только повысится.


  1. Nnnnoooo
    31.01.2022 15:51

    CQS

    Насчет бесполезности CQS, выше озвученный бред мог написать только тот кто вообще не понял зачем это. И это учитывая что CQS значительно более общий принцип, который можно (а часто и нужно) применять в большинстве приложений (также как и с разделением бизнес логики и представления). И это в отличие от CQRS, что значительно более конкретная вещь, которую везде пихать смысла нет.


  1. CrocodileRed
    31.01.2022 16:21
    +1

    И эти люди поставляют нам кадры... :-))

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


  1. karevn
    02.02.2022 00:35
    +1

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