Каждые 5 лет количество программистов увеличивается в два раза. И об этом знают уже все, кто хотя бы раз смотрел или читал Боба Мартина. Программное обеспечение есть повсюду. Вы не можете найти в мире такое место, где в радиусе трёх метров от вас не было бы программного обеспечения (на самом деле это про пауков, кажется, но я же просто шучу). И вот если обратить внимание, то окажется, что в ваших часах есть ПО, в вашем телефоне очень много ПО, в холодильниках у некоторых есть ПО, во всех современных автомобилях есть ПО, даже в некоторых собаках (не шутка) уже есть ПО и этот список можно долго продолжать.

Разработчик программного обеспечения в нашем современном мире имеет самое большое влияние. Буквально нет ни одного уголка человеческой жизни на этой планете, на который не повлияло бы качество нашего программного обеспечения Composing Software Eric Elliott. Но вместе с ростом количества разработчиков в современном мире растёт ли качество программного обеспечения? Да, количество разработчиков растёт, но растёт ли количество хороших разработчиков?

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

▍ Откуда ноги растут



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

Я был немного удивлён и решил уточнить этот вопрос в чате, где обитают backend разработчики. Оказалось, что каждый седьмой из них придерживался такой точки зрения: “SOLID это только про ООП”. Интересно, откуда это взялось? Ответ на поверхности: если вы введёте в google слово SOLID, то самая верхняя ссылка будет, конечно, на википедию, где страница прямо так и озаглавлена: “SOLID (объектно-ориентированное программирование)”. Все следующие за ней ссылки также вторят — “объектно ориентированное программирование”, но уже не в заголовке, а в основном тексте. Чтобы развеять это заблуждение, я обращаюсь к истокам.

Работу над этими принципами Боб Мартин начал ещё в конце 80-ых прошлого века, он обсуждал их в USENET с другими разработчиками. И тема обсуждения была «Принципы проектирования программного обеспечения». Позже в 2000 году вышла книга Design Principles and Design Patterns (Принципы и шаблоны проектирования), где он впервые опубликовал свои принципы (и это ещё не называлось SOLID). Чуть позже в 2004 году Майкл Физерс прислал ему письмо, где сказал, что если он переставит свои принципы местами, то может собрать слово SOLID.

Теперь Дядя Боб рассказывает об Object Oriented Design или даже Object Oriented Agile Design, и это тоже не ООП, а проектирование. Так что SOLID – это не про ООП и никогда не было про ООП. Сам первоисточник указывает на это в своей книге “Чистая архитектура”.

И вот тут я процитирую один комментарий с сайта Роберта Мартина:

You begin by talking about OOD and by the end of the first paragraph have made the most critical of errors: equating OOD with OOP.

Перевод:

Вы начинаете говорить об OOD и к концу первого абзаца совершаете самую критическую из ошибок: приравниваете OOD к OOP.

▍ Проблемы плохого дизайна



Существует ряд проблем, к которым приводит плохой дизайн. Эти проблемы упоминает Боб Мартин в своей книге “Принципы и шаблоны дизайна” в 2000 г. И эти проблемы он называет симптомами “гниения”.

Ознакомиться с кратким изложением этих симптомов можно под спойлером
Жёсткость. Если приложение довольно давно развивается без рефакторинга, то в какой-то момент оказывается, что новые изменения вносятся с трудом. Разработчик тратит много времени, и менеджеры начинают так приоритизировать задачи, что low и middle-level дефекты могут быть просто отложены (навсегда) в долгий ящик. Поверьте, так это и происходит.

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

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

Вязкость. Когда разработчик должен внести изменение в код, чаще всего он находит несколько способов это сделать. Некоторые из этих способов сохраняют дизайн приложения, а некоторые нарушают его дизайн — такие изменения ещё называют “хаками”. Проявление вязкости дизайна — это, когда вносить сохраняющие дизайн изменения — становится труднее, чем просто внести “хак”.

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

Есть определённые подходы к разработке приложения, которые позволяют создать и поддерживать гибкий и мобильный дизайн, который называется Чистая Архитектура. Сам код приложения становится понятным и податливым для внесения изменений. Но каждый раз с каждым новым проектом разработчик встаёт на пути соблазна: “напишу MVP по-быстрому, а потом займусь рефакторингом и увижу, как лучше было бы сделать”. Не стоит поддаваться. Это не работает и тому есть бесконечное количество доказательств. Если хотите хороший код, нужно писать его с учётом архитектуры, и принципы SOLID как раз помогают в этом.

Утверждаю ли я, что на этих принципах свет клином сошёлся? Нет. Пока я работал над этой статьёй, мне попалось на глаза вот такая статья, где автор рассуждает о том, кому нужно следовать принципам, а кто уже понимает, для чего эти принципы были разработаны и сам принимает решения, когда им следовать, а когда — нет. Я с ним полностью согласен. Правила разрабатываются тогда, когда назревает критический момент, и становятся простой формальностью для тех, кто понимает причины возникновения этих правил. Но, если вы заглянете в код такого разработчика, вряд ли вы увидите там Комок грязи, это будет всё тот же в определённой степени SOLIDный код.

▍ Следы чистой архитектуры



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

Возьмём, например, микросервисную архитектуру. Гайдлайны Криса Ричардсона говорят о том, что для разделения приложения на микросервисы вы можете применить декомпозицию по бизнес-возможностям (функциям) или декомпозицию по предметным областям, следуя Domain-Driven Design. Первый подход в явном виде разделяет продукт по границам бизнес-функций, а второй делит согласно бизнес-моделям (это, если кратко). Я вижу, что в какой-то степени оба этих подхода отвечают SRP (Принципу единственной ответственности), который является первым принципом из SOLID. Ведь в случае с первым изменения будут исходить от владельцев бизнес-функций, а во втором изменения будут инкапсулированы в модели.

И для убедительности я приведу цитату из книги Ричардсона:

Decompose by subdomain and Decompose by business capability are the two main patterns for defining an application’s microservice architecture. There are, however, some useful guidelines for decomposition that have their roots in object-oriented design.

Перевод:

Декомпозиция по поддоменам и декомпозиция по бизнес-возможностям — это два основных шаблона для определения архитектуры микросервисов приложения. Однако есть несколько полезных рекомендаций по декомпозиции, которые уходят корнями в объектно-ориентированное проектирование.

Далее он рассказывает о принципе единственной ответственности и ссылается на Боба Мартина.

Если хорошо постараться, то можно связать паттерн API Gateway с DIP (Принцип инверсии зависимостей). Ведь клиенты не общаются с сервисами напрямую, а сервисам не нужно придерживаться общего с клиентами контракта для взаимодействия. Вместо этого — клиенты общаются с gateway, а сервисы регистрируют в нём свои конечные точки. Вот что Боб Мартин пишет об актуальности этого принципа: “мы не хотим, чтобы наши бизнес-правила высокого уровня зависели от деталей низкого уровня” в своей статье Релевантность SOLID.

Я совсем не пытаюсь натянуть сову на глобус. Я всего лишь хочу сказать, что SOLID — это не только про ООП. Но в чём причина моих стараний?

▍ Аргументация



Я стараюсь расставлять вещи по своим местам для того, чтобы можно было свободно аргументировать свои рассуждения в общении с коллегами.

Доводилось ли вам когда-нибудь в диалоге с коллегой по цеху аргументировать свою точку зрения одной из этих фраз: “так никто не делает”, “это так не работает”, “не надо так делать”? Если доводилось, то вы наверняка знакомы с ответом-вопросом “почему” и аргументами с которыми трудно спорить: “я сто раз так делал и всё работает” и подобными из той же серии. К чему приводят такие диалоги? Ни к чему. Вы остаётесь при своём мнении, а коллега остаётся при своём. Совершенно невозможно определить кто прав, а кто нет.

А теперь представьте, что вот так аргументировали свои доводы: “это нарушает принцип единственной ответственности” или “следуй принципу KISS”, или “правило G34: используй один уровень абстракции на функцию”. И теперь все вопросы “почему” можно адресовать гуглу. Не понимаешь о чём я — иди гугли, все материалы есть в открытом доступе и там полно аргументов и примеров.

Да, это уже не принципы SOLID, а, вернее, не только они. Но я и не хочу ограничиваться. Я не говорю, что мы должны зубрить только SOLID, как сумасшедшие, и формировать из них квесты для собеседований при приёме на работу. Я просто хочу донести до читателя тонкую мысль: мы должны понимать то, что мы делаем и уметь доказывать, что наши подходы верны. Согласитесь, если с вами разговаривает человек, который может аргументировать свою позицию, то такой диалог может привести к выгоде для обеих сторон. С одной стороны, будет пресечена вероятная ошибка, а с другой стороны — будет получен бесценный опыт.

▍ Основные заблуждения



Но я хочу предостеречь вас от возможного неверно представленного материала. В наше время существует бесконечное множество источников информации. От книг и статей в интернете до глобальных индексаторов вроде google или yandex. Где-то между двумя крайностями этого спектра находятся такие ресурсы, как youtube и wikipedia, где разместить информацию может как авторитетный автор, так и несведущий в теме человек. Как определить, какой статье следует верить, а какой нет?

Все уже знакомы с принципами SOLID, а вы уверены, что вы получили материал без искажений? Ответьте на вопрос: что означает Принцип Единственной Ответственности? Если вы ответите что-то вроде “функция (или модуль) должна делать что-то одно или функция (или модуль) должна отвечать за что-то одно”, то я вас поздравляю, вы прослушали (или прочитали) искажённый материал.

Самая распространённая ошибка (кроме ошибки доверять не авторитетным авторам, а таким, как я — писакам) в том, что SOLID можно применять только к ООП, а вторая в том, что Принцип Единственной Ответственности — это про то, что делает функция. Сам Роберт Мартин подмечает, что это распространённое заблуждение. Действительно, существует принцип, который говорит, что функция должна выполнять что-то одно, но это Инкапсуляция, а не то о чём мы с вами сейчас говорим (не SOLID). Не ждите, что я объясню вам, как правильно понимать этот принцип, если хотите знать — читайте автора.

А как насчёт Принципа Подстановки Лисков? Я видел невыносимые объяснения этого принципа на просторах интернета, когда при объяснении, демонстрировали имплементацию абстракции, в которой один из методов закрыт вызывающей исключение заглушкой. Следование рекомендациям таких советчиков не убережёт вас от нарушения этого принципа. И вам будет казаться, что вы все делаете правильно, но проект всё равно “загнивает”.

Если вы наберёте в гугле слово SOLID, то обнаружите, что все заголовки содержат в себе фразу “Объектно Ориентированное Программирование”. А это означает, что всякий разработчик, который решил программировать в парадигме процедурного программирования (или функционального), автоматически будет игнорировать эти простые правила дизайна. А нарушение этих принципов приводит к “загниванию”. Или вы хотите сказать, что загниванию подвержен только код, написанный в парадигме Объектно-Ориентированного Программирования?

▍ Влияние SOLID на меня, как на разработчика



Давайте немного поговорим обо мне. Я 16 лет в разработке коммерческого программного обеспечения. Я познал боль разработки на Delphi, писал стометровые процедуры на T-SQL, некоторое время разрабатывал на Python и примерно такое же время на одном замечательном функциональном языке flow9. Всё это время мне дико не нравился мой собственный код! А что уж говорить о чужом коде, в котором я нередко находил баги, связанные с плохим дизайном?

Долгое время я пытался понять, что не так с кодом? Как сделать так, чтобы сложность изменения была линейно зависима от количества строк. Когда проект ещё маленький, мы программируем быстро, ведь нам приходится просто писать код, периодически его запуская, чтобы проверить, что он компилируется и (может быть) работает. Но со временем код обрастает зависимостями, какой-то участок кода использует другой участок кода, а тот другой тянется к третьему. И вот нам уже нужно не только писать код, но и заботиться о том, чтобы не сломать существующий.

Первое, что становится понятно — без модульных тестов никуда. Но как же сложно иногда бывает покрыть код модульными тестами, особенно если мы используем в нашем коде какие-то зависимости, например, от базы данных. Давным-давно я начал догадываться, что должны существовать такие правила, которые позволяют писать код, подверженный влиянию зависимостей в меньшей степени. Который позволит держать сложность внесения изменений в код линейно зависимой от кол-ва строк, а сложность написания модульных тестов вообще константой. Я что-то читал, что-то пытался найти, но не очень активно. Ведь я, как и все ленивые программисты — не очень люблю теории. Читать что-то, пытаться понять и использовать в практике — это такая скукота. Нужно просто писать код, разве нет?

Первая книга, которая повлияла на мою работу, была “Чистый код” Боба Мартина. Я прочитал и понял, что это то самое, о чём я столько времени догадывался. Все эти запахи кода и эвристические правила… Я понял, что теперь в диалоге с коллегами могу конкретно указать, что мне не нравится в коде, сославшись на авторитетное мнение.

Вторая книга “Чистая архитектура” оказалась именно тем, что я искал. Описанные в этой книге подходы помогают сделать код независимым, понятным и податливым для изменений.

Через некоторое время я заметил, что пишу код, который действительно легче изменять. И в качестве доказательства этого наблюдения мне послужила одна маленькая история: как-то раз мне понадобилось внести изменение в один из моих старых пет-проектов. Это был whatsapp-бот, а нужно было добавить туда возможность читать и отвечать на сообщения ещё и из telegram. И что? Я не смог этого сделать — архитектура этого маленького проекта оказалась настолько вязкой, что я решил не трогать, чтобы ничего не сломать. Ведь гораздо проще оказалось это переписать, чем изменить. В это же самое время, какие-то изменения в моих текущих проектах делаются с теми же небольшими усилиями, как и прежде. Кроме того, мой текущий код гораздо податливей для рефакторинга, потому что имеет меньше зависимостей и покрыт тестами.

▍ Чего же я хочу?


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

Далее я хотел бы, чтоб вы взяли и почитали Роберта Мартина, Мартина Фаулера и других отличных людей, которые пишут про архитектуру и про рефакторинг. Попробуйте прочитать не для галочки. Попробуйте применить те подходы, которые они описывают. Расширьте своё понимание принципов SOLID, это не только ООП, это принципы чистой архитектуры.

Ну и наконец, давайте перестанем воспринимать искажённую информацию. Все эти википедии и “простым языком о …” на ютубе — это всё скользкая дорожка. То, о чём говорят такие вот “лекторы” чаще всего банально и неэффективно. Может показаться, что материал авторитетных авторов сложен для восприятия, ведь он не “простым языком о …”. Но, в конце концов, лучше два раза прочитать Боба Мартина и понять только половину, чем сто раз просмотреть бесполезный ролик на ютубе. Но я не буду ругать ютуб, там тоже можно найти отличный материал, например, на канале “HighLoad++” или “GOTO;”.

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

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


  1. koreychenko
    20.06.2022 13:17
    +16

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


  1. JustPeople
    20.06.2022 15:21
    +10

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


    1. Newbilius
      21.06.2022 12:47
      +4

      Эта статья решает задачу кого-то из маркетологов ruvds, у которого премия зависит от количества постов в корпоративном блоге на хабре… и судя по всему, решает достаточно успешно :)


      1. JohurN
        22.06.2022 11:34
        +2

        пойду с этим комментом к начальству, мне явно где-то премию зажали…


  1. lymes
    20.06.2022 17:18
    +1

    Все эти запахи кода и эвристические правила…

    Только недавно озадачивался как перевести code smell в итальянской документации по стат анализу проекта в SonarQube. https://ru.wikipedia.org/wiki/Код_с_запашком


  1. AlexunKo
    20.06.2022 18:57
    +1

    Хороший, обстоятельный материал. Вообще, вдумчивость и следование принципам - это похвально. Но мне кажется, этого мало. Лично я вижу за любыми принципами универсальные интуиции (идеалы), приходящие с опытом естественным образом, из практики. Более того, решающее значение имеет не столько само следование принципу, сколько мера (разумная достаточность). Вы видели вещи, доводимые до абсурда в состоянии, например, опьянения перфекционизмом? Я - да. И сам грешил! И как легко в этом случае прикрыться принципом, обманывая себя и других (банальная рационализация) необходимостью такой работы. Поэтому, смысла в их декламировании я большого не вижу. Уж сколько раз сталкивался с прекрасно заученными концепциями, которые почему-то упускаются или применяются "через край" (здрасте, микросервисы), как только начинается реальная работа, а не досужие разговоры.


    1. HellWalk
      21.06.2022 09:42
      +1

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


  1. jdev
    21.06.2022 05:59
    +1

    И вот тут я процитирую один комментарий с сайта Роберта Мартина:

    А почему вы не цитируете ответ Мартина?:)

    You begin by talking about OOD and by the end of the first paragraph
    have made the most critical of errors: equating OOD with OOP. OOA/D is
    about THINKING. OOP is about DOING. The two are separate.

    * It took me over half a decade to realize that this wasn't true. OOP and OOD are inseparable. OOA is undefined. - UB

    Но вообще я с вами согласен ООП != ООД.


  1. pilot114
    21.06.2022 06:37

    Блин, а я думал в статье будут конкретные примеры, очень интересно как эти принципы будут реализованы в процедурном стиле. В идеале - по мотивам https://github.com/torvalds/linux, который как раз представляет собой гигантский проект с приемлемым качеством и реализованный в полностью процедурном стиле. Это было бы гораздо убедительнее.


  1. alxt
    21.06.2022 10:09

    Проблема в том, что понятие "Абстракция" применяется только к ООП. Хотя, если подумать, это более общее, чем ООП.

    Собственно ООП это одна из абстракций, так же как ФП, базы данных и прочее.

    А SOLID это просто список критериев хорошей абстракции. Но SOLID сформулирован в терминах ООП.


  1. manyakRus
    21.06.2022 11:13

    В базах данных уже есть принципы нормализации №1...№6 обязательные для всех нормальных dba. А в программировании нет никаких принципов.

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


    1. fireSparrow
      21.06.2022 12:35

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

      То же относится и к коду — он должен писаться осознанно и с пониманием того, как принимаемые решения соотносятся со стоящими задачами. Если кто-то пишет код по методичке — то это не программист, а кодер.


    1. jdev
      21.06.2022 19:25

      Бесстыжий плаг

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

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

      Согласованный набор таких принципов я пытаюсь собрать в Эргономичном подходе: https://azhidkov.pro/posts/22/04/220409-ergo-approach-v10m1/