Такие вещи как SOLID не редко можно услышать в кулуарных разговорах или что еще чаще на собеседовании. Но отчасти не зная в точности SOLID, каждый программист бессознательно, но придерживается данных принципов, когда есть на то время)) Но почему нам как программистам важно знать в точности смысл таких аббривиатур или, если вам угодно, тех или иных смыслов из нашей профессии. А всё просто - это как минимум солидно, или вернее сказать - не знать этого не солидно. (подробнее об этом в P.S.)

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

SRP

Single Responsibility Principe - Принцип единой ответственности.
Каждый объект должен стремится к одной зоне ответственности и к одной причине для существования или если проще говоря: каждый класс должен иметь одну обязанность и, следовательно, одну причину для изменения.

Ну, по моему мнению, это один из самых главных принципов, заключенных в SOLID. Его разумное применение избавляет от многих головных проблем, те кто совместно работает над классами-монстрами и ломает голову при конфликтах - поймут меня, но статья не об этом.

Стратегия. Выносим реализацию определенного алгоритма в отдельный класс и это нам дает:

  • Наш класс и непосредственно сам алгоритм имеют свою зону ответственности и не мешают друг другу +1 SRP

  • Если в алгоритмах нужно что-то поменять, то у нас нет причин изменять сам класс родитель. И в том и в другом случае это плюс к одной причине для изменения. +1 SRP

Фасад. Что бы ваш класс не зависел от какой-то внешней сложной системы, создайте просто фасад для этой системы, что это нам дает:

  • Наш класс и непосредственно сама работа с системой имею свою зону ответственности и не мешают друг другу. +1 SRP

  • Если в работе с системой что-то нужно поменять, то у нас нет причин изменять сам класс родитель. И в том и в другом случае это плюс к одной причине для изменения. +1 SRP

DDD. Разбивает всю модель предметной области (домен) на поддомены. У каждого поддомена своя модель данных, область действия которой принято называть ограниченным контекстом +1 SRP

Database per service. Каждый сервис имеет свою базу данных для работы, и каждая база имеет лишь один сервис для работы которой он может быть изменен +1 SRP

CQRS. Если в простой форме, то теперь у нас логика записи и чтения данных раздельно существуют и имеют разную область ответственности.

OCP

Open-closed Principle - Принцип открытости-закрытости.
Программные сущности (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для изменения.

Стратегия. Выносим реализацию определенного алгоритма в отдельный класс и это нам дает: теперь нет необходимости лезть в родительский класс, для добавления новой логики. +1 OCP

Адаптер. Данный паттерн предназначен для организации использовании функционала объекта, недоступного или нежелательного для модификации, и у меня такое ощущение, что данный паттерн именно что и создан для решения проблемы отсутствия OCP в коде. Пусть система для которой мы пишем адаптер и не стремилась быть гибкой для всех, но мы расширим ее возможности без потребности лезть во внутрь. +1 OCP

Декоратор. Добавляем дополнительное поведение объекту, не меняя внутренности других декораторов и самого класса. +1 OCP

Вообще стоит отметить что паттернов которые помогают нам придерживаться данного принципа вагон и маленькая тележка: Шаблонный метод, Мост и тд. +10 OCP

LSP

Liskov Substitution Principle - Принцип подстановки Барбары Лисков
Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы.

Шаблонный метод. Было странно не упомянуть данный паттерн при разборе данного принципа, так как и данный паттерн и данный принцип на всю используют возможность полиморфизма.

Декоратор. При реализации данного паттерна необходимо так же придерживаться данного принципа, так каждый подключаемый декоратор подразумевает добавление дополнительного поведения объекту, а не изменение его логики до неузнаваемости, так что трудно будет отследить где-же все пошло не так как надо +1 LSP

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

  • UnsupportedOperationException - тогда когда мы получаем к примеру список и ни в чем не подозревая, пытаемся добавить в него новый элемент. Да, многие методы реализации интерфейса List, который возвращается, к примеру, тем же List.of(...) выбрасывают данное исключение

  • PSQLException - когда мы к примеру вызываем метод execute (String sql) у нашего Statement, а он в свою очередь является реализацией PreapredStatement postgres драйвера.

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

ISP

Interface Segregation Principle - Принцип разделения интерфейса
Клиенты не должны зависеть от методов, которые они не используют, в общем, это про правильное разделение интерфейсов.

Backends for frontends. Кроме всех остальных возможностей, данный архитектурный паттерн позволяет нам собрать запрос для определенного клиента в необходимом для него виде, тем самым мы можем не делать общее api для всех клиентов, путая их или ограничивая в чем-то, а реализовать свой вариант для каждого, разделив все по необходимости +1 ISP

DIP

Dependency inversion principle - Принцип инверсии зависимостей
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. То есть, абстракции не должны зависеть от деталей, детали должны зависеть от абстракций.

Создать абстракции это одно, но непосредственное конкретная инициализация объекта это другое, то есть IoC, и здесь так же мы не должны зависеть от деталей, то есть забыли про ключевое слово new

IoC. Inversion of Control - это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов. +1 DIP

Фабричный метод. Создание того или иного объекта выносится за пределы класса, тем самым освобождая его от той или иной реализации +1 DIP

Фасад. Переносим логику работы некоторой системы в соответствующий класс и получаем - наш модуль не зависит от деталей, то есть не связан с сложностью той или иной системы, и если зависит то только от абстракции фасада +1 DIP

Observer. Реализуем у класса механизм, который позволяет объекту этого класса получать оповещения об изменении состояния других объектов и тем самым делаем связь между объектами слабосвязанной и менее зависим от других классов (абстракций) впринципе +1 DIP

P.S.

Почему же знать такое солидно?

Был раз я гулял по одному красивому европейскому городу, я любовался им впервые, но в какой-то момент я заметил одного человека, белокурого и с ясными голубыми глазами, который явно с неприкрытым удивлением наблюдал меня, а в частности, его привлекли мои излияния всему меня окружающему, а точнее, ну как будет видно далее, их форма. Это продолжалось недолгое время и он в какой-то момент оказался рядом и поздоровавшись со мной на английском, спросил у меня: родом ли я из России. Честно говоря, я немного замешкался, так как мой английский что тогда, что и сейчас, доставляет мне много неудобств в живом разговоре, но более-менее столь простой вопрос я смог расшифровать и ответил или даже кивнул в знак согласия. Он же улыбнулся и ответил: мол, и я тоже, и более того он сказал, что он русский. После этих слов (конечно же, мне пришлось немного подумать) я тоже в ответ улыбнулся и был рад, надеясь, что хоть некоторую часть слов я смогу себе позволить на русском, и немного подумав, спросил уже на русском: аа, как дела ? Нууу... тут уже с другой стороны ответ заставил себя подождать, но все же через секунды две человек ответил что не понял меня от слова совсем и вообще не говорит по-русски. Я немного помялся (мне нужно было тоже понять, что он не понял меня), и я переформулировал свой вопрос на английском и собеседник в свою очередь ответил: мол, что не очень, я же любезно поинтересовался что случилось, а он ответил что-то вроде - не задался день с самого начала. Я сочувствующим взглядом ответил что мне жаль и что все наладится (это я умею) и после этого я решил более не продолжать разговор так как нам уже не о чем было говорить, что собственно тогда я и сделал, пожав ему руку и быстро удалившись.

К чему же это я, вы спросите ?! Вернемся к тому мужчине, думаете почему я не хотел с ним более стоять или продолжить знакомство, хотя его радостное лицо при упоминании что я из России меня обрадовало ?! Я общительный малый, да и мне было бы приятно услышать родную речь, но... Оказалось что человек за всю свою осознанную жизнь не удосужился хоть немного погрузится в родной язык (хоть и смог среди толпы меня услышать) перед тем как кричать: я из России... и тд. Вы должны меня понять, я не имею ничего против его национальной идентичности и не говорю что он не является тем кем он представляется, нооо... это просто навсего не солидно, ребята. Разве он не знает что такое how are you ? Нет, он знает как поинтересоваться у человека о его делах, и знает как по нашенски от души сказать о своих делах: не очень. Так же знание тех же слов не сделает его русским и не вселит в него любовь ко всему русскому, совсем нет, но важна форма, которая поможет и нам самими собой и с другими людьми делится лучше тем самым кем мы себя представляем.

Так же и в программировании, мало сказать что ты программист, здесь оооочень много вещей которые не делают нас программистами на деле, но вот так, между делом, в расслабленном режиме, в разговоре с коллегами, стоит один раз ответить что-то касательно вопроса, ну к примеру, о SOLID, или даже может высказать свое личное особенное мнение на этот счет - нууу это солидно, немножко пусть, но SOLIDно!

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

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


  1. zubrbonasus
    02.05.2024 17:32
    +2

    Принцип единственной ответственности (single responsibility principle): Модуль должен отвечать за одного и только за одного актора это подметил Р. Мартин в книге «Чистая архитектура. Искусство разработки программного обеспечения», когда описывал эволюцию данного определения.


    1. Andrey_Solomatin
      02.05.2024 17:32

      Про единственную причину для изменений он же часто говорит в видео.


  1. imanushin
    02.05.2024 17:32
    +6

    Довольно занятно, как в реальных проектах ломаются все озвученные принципы:

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

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

    Если в алгоритмах нужно что-то поменять, то у нас нет причин изменять сам класс родитель. И в том и в другом случае это плюс к одной причине для изменения. +1 SRP

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

    Декоратор. Добавляем дополнительное поведение объекту, не меняя внутренности других декораторов и самого класса. +1 OCP

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

    Liskov Substitution Principle - Принцип подстановки Барбары Лисков
    Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы.

    Если исключить самые-самые простые примеры, это принцип не работает даже в небольших проектах. Несмотря на то, что интерфейс к базе может быть общий, синтаксис запросов будет очень разным (как пример). Несмотря на то, что существует интерфейс List в Java (IList в .Net), в реальной программе всё-таки требуется знать, изменяемый объект или нет, это ArrayList или LinkedList и так далее. Аналогично про интерфейс Map (это может быть и хеш таблица, и дерево).

    Interface Segregation Principle - Принцип разделения интерфейса
    Клиенты не должны зависеть от методов, которые они не используют, в общем, это про правильное разделение интерфейсов.

    Возможно, это странный перевод, так как википедия говорит, что:

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

    И, в целом, идея даже правильная, вот только даже List, приведенный в пример выше, уже нарушает этот принцип. Собственно, если честно следовать этой логики, то у каждого интерфейса должен быть строго один метод, иначе можно быстро найти пример, когда код нарушает правило.

    Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. То есть, абстракции не должны зависеть от деталей, детали должны зависеть от абстракций.

    Это очень спорное правило, на самом деле. Как раз наоборот - если я использую драйвер к базе, я четко осознаю, как он работает. Если я читаю строки из файла, я именно понимаю, как всё будет происходить. Более того, если мы не делаем библиотеку, то мне намного легче видеть, что конкретно вызывается (с поправкой на unit тесты, где могут быть подмены), вместо того, чтобы иметь еще один проект посередине, чтобы каждый раз просить IDE перейти к реализации. Собственно, я привел примеры выше про List и Map.

    Более того, если представить, что нам захочется сменить базу данных, то такая мелочь, как смена типов в программе, будет незаметна даже на фоне созвонов и договоренностей с Dev Ops.

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

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

    Заодно правило выше будет толкать к написанию правильных текстов, к более читаемым и строгим типам, к наличию необходимой документации и корректного ревью кода.


    1. janvaljan Автор
      02.05.2024 17:32
      +1

      Спасибо за ваш анализ, приятно было ознакомится. Решил так же прокомментировать некоторые части.

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

      ....

      И, в целом, идея даже правильная, вот только даже List, приведенный в пример выше, уже нарушает этот принцип. Собственно, если честно следовать этой логики, то у каждого интерфейса должен быть строго один метод, иначе можно быстро найти пример, когда код нарушает правило.

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

      Если исключить самые-самые простые примеры, это принцип не работает даже в небольших проектах. Несмотря на то, что интерфейс к базе может быть общий, синтаксис запросов будет очень разным (как пример). Несмотря на то, что существует интерфейс List в Java (IList в .Net), в реальной программе всё-таки требуется знать, изменяемый объект или нет, это ArrayList или LinkedList и так далее. Аналогично про интерфейс Map (это может быть и хеш таблица, и дерево).

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

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

      Тут согласен, сигнатуры это, в некотором роде, Ахиллесова пята.

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

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


      1. imanushin
        02.05.2024 17:32
        +1

        они ожидают одинаковой логики от этих абстракций, и в большинстве случаев получают ее.

        Я не до конца этого понимаю.. Вот есть у меня Map в переменной. Могу ли я в цикле проверять "есть ли элемент в Map", если число проверок будет столько же, сколько и размер Map? Для HashMap - однозначно, а вот для TreeMap я свалюсь в NlogN (N проверок по logN), плюс в Java будут еще проблемы с нелокальностью памяти (но это, зачастую, уже мелочи).

        Аналогично про List - отличный интерфейс, вот только могу ли я спокойно сохранить его в переменную (так как он неизменный, как и 99% List'ов в программе) или нет? А могу ли я добавлять туда элементы (так как это копия) или же я получу ошибку?

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

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

        Мне всё-таки так не кажется. Функциональный подход (для JVM это будет Scala/Kotlin) дает намного больше пользы. Он автоматически покрывает DDD (по сути, DDD из него и выходит), он сразу применяет Open-closed Principle (за счет того, что намного проще скрывать не только классы/методы, но и область видимости переменных) и так далее.

        SPR не требует от нас ограничиваться одним методом, он лишь призывает к тому что бы класс, а именно его состояние и поведение, были ограничены одной зоной ответственности

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


        1. DenSigma
          02.05.2024 17:32
          +2

          Логика абстракций - это Map, карта на один ключ одно значение. Оно и используется для бизнес-операций. А вот в виде хэша или в виде дерева хранить данные внутри - это детали реализации. В этом и выгода идей Мартина. На верхнем уровне в бизнес-классе вы используете только переменную интерфейса. А какой конкретно класс реализует - бизнес класс знать не должен и ему неинтересно. Dependency Injection и решает эту задачу - при создании безнес-класса, в полях которого прописано Map map, внешний фабричный метод (или spring, для определенности), подсовывает нужный программисту объект класса реализации. Разработчик бизнес-класса не парится насчет списка. При необходимости настройки того, где конкретно будут храниться данные, можно будет легко сменить тип map, без исправления бизнес-класса.


        1. Andrey_Solomatin
          02.05.2024 17:32

          Логирование это скорее сайд эффект поэтому бизнесу на него пофиг. Тем более вы только вызываете функции логгера. Если нужно будет поменять формат логгирования ты вы свой бизнес класс не должны менять.

          Проверку входных данных лучше отделять от логики. Так тестировать проще.


    1. DarthVictor
      02.05.2024 17:32

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

      Если у класса один метод, то этот класс скорее всего должен быть функцией.


    1. DenSigma
      02.05.2024 17:32
      +1

      Не надо путать единственность ответственности с единственностью метода.


    1. Andrey_Solomatin
      02.05.2024 17:32
      +1

      UnmodifiableList в джава нарушает LSP. Это приводит к проблемам с использованием этого класса приведённого к типу List. Это как раз аргумент за то, что этот принцип работает.

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


  1. AlexunKo
    02.05.2024 17:32
    +6

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


    1. janvaljan Автор
      02.05.2024 17:32

      Но отчасти не зная в точности SOLID, каждый программист бессознательно, но придерживается данных принципов, когда есть на то время))

      ...

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

      Я с вами отчасти согласен, и я свою статью начал и закончил похожими тейками.


  1. gochaorg
    02.05.2024 17:32

    Есть несколько статей, которые критикуют эти принципы, без критики SOLID принципов статья будет не полной

    https://habr.com/ru/articles/555862/

    https://habr.com/ru/companies/skbkontur/articles/260781/

    https://temofeev.ru/info/articles/istoriya-vozniknoveniya-cupid-kritika-solid/

    https://ru.stackoverflow.com/questions/551346/Когда-НЕ-нужно-использовать-solid


  1. guryanov
    02.05.2024 17:32

    Ну вот SRP вы совсем не тот написали, про который Роберт Мартин писал. Там идея в том, чтобы код, который нужен разным акторам (у него в примере это администраторы баз данных, отдел бухгалтерии и отдел по работе с персоналом) лежал в разных модулях и не использовал общих функций.

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

    Если что - я не сторонник SOLID, просто постарался максимально понять эти принципы.


    1. Andrey_Solomatin
      02.05.2024 17:32

      Копипастить сразу не надо. А вот когда надо будет внести изменение нужное одному актору, нужно будет применить принцип закрытости и создать новый класс (копипаста, наследование, композиция).


  1. j_e_s_t_e_r
    02.05.2024 17:32

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


  1. Ksnz
    02.05.2024 17:32

    Когда уже Шаблонный метод начнут вносить в антипаттерны?

    Он нарушает принцип composition over inheritance, и вынуждает реализации знать о внутренностях суперкласса


    1. janvaljan Автор
      02.05.2024 17:32

      https://habr.com/ru/articles/325478/
      В данной статье ознакомился с "Composition over inheritance", в общем смысле понимаю проблему, и к чему нас призывает сам термин, но даже в этой статье упоминается:

      Наследуем, если:

      • Наследник является корректным подтипом (в терминах LSP — прим. пер.) предка

      А вы как можете убедится, Шаблонный метод я упомянул вместе с LSP


    1. janvaljan Автор
      02.05.2024 17:32

      Так же хочется отметить, что я описал проблемы которые встают перед разработчиками когда они активно используют наследование, те же не поддерживаемые методы из реализации `List.of` и тд.


  1. NeoNN
    02.05.2024 17:32

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