Pull to refresh
4
0
Send message

Привет. Спасибо за комментарий, глубоко копнул.

Смысл этого принципа же не в том чтобы просто сказать "код надо декомпозировать". Это и так все понимают, что надо. Вопрос в том - когда?

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

А как узнать содержит или нет?

У нас в таске пишут описание задачи. Например добавить функционал передачи пермишенов одного сотрудника на время его отпуска другому. Таска подразумевает хранение в базе (или другом хранилеще) связи пользователей, какой нибудь код чтоб в эту базу ходить и рефакторинг пермишенов. Получается минимум три класса: класс с перишенами, класс для работы с базой, класс модели алхиии и возможно схемы пайдентика. Если будет меньше то придется обосновывать свой выбор. Самый лучший вариант узнать это обсудить до начала работ с командой, с теми кто будет делать ревью.

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

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

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

Я уверен что примеры отражают идею, которая стоит за изначально сформулированнвм принципом. А для переформулированого определения нужны модули которые будут работать с инстансом AuthUserKeycloak где определен actor и его пермишены. В моем случае модуль это инстанс миктосервиса который реализует доменную логику. Получается цепочка actor инициирует команду которая запускает доменную логику в микросервисе. Это сильно круто для данной статьи и для целевой аудитории на которую данная статья расчитана.

И Kergan88, надеюсь что наше обсуждение поможет какому нибудь начинаюшиму програмистаму получить оффер. :)

Привет.

Принцип не изменился со временем. Мартин просто переформулировал определение.

Формулирование нового определения не делает старый неверным.

У меня есть классы к которым я не могу применить обновленный принцип. Например классы нотификации через email и через например sms. В них нет actor. Обе нотификации может инициироватьодин actor но я же не буду из-за этого валить все в один класс.

Или классы аутентификации и авторизации когда у нас уже как бы есть actor но мы еще не знаем это Guest, Registered user, Administrator или кто то еще. А пока мы этого не знаем то мы не можем его отнести к какому-то модулю.

Как применить A module should be responsible to one, and only one, actor. если actor нет или он не определен.

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

Слушай я вижу что ты начитанный и теорией владеешь. Но не совсем понимаю какую мысль ты хочешь до меня донести.

  • Что старое определение неверно?

С моей точки зрения определение, данное Робертом Мартином в начале двухтысячных совершенно. Я считаю что его можно и нужно применять (помоему это единственное место с которым ты не согласен :). Очень грустно что у когото оно вызывает трудности.

  • Что надо использовать только переформулированное определение?

Да новое определение замечательное, но оно про более крупные абстракции чем классы. Оно про модули.

  • Что нужно поменять формулировку определения принципа SRP в статье?

Мой опыт показывает что начинающеммому разработчику лучше ставить задачи по добавлению нового функционала внутри модуля. А для таких задачах переформулированное определение не подходит. В правильной архитектуре в модуль уже приходит конкретный actor. Но внутри модуля старое определение отлично подходит. И код получается солидный. Когда разработчик дорастет до того уровня что будет сам проектировать модуль, нарезать таски и раздавать их мидлам и джунам тогда он воспользуется определением A module should be responsible to one, and only one, actor. Разработчику такого уровня уже будет не нужна эта статья. :)

SRP описан неверно и приведен неверный пример, собственно, как и почти везде в интернете.

Все нормально. Это перевод. Переведено правельно.

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

Вот ссылка на статью 2014 где Роберт Мартин дает пояснения.

https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html

Это скорее разяснения причем сделанное с точки здения DDD. В книге Martin, Robert C. (2018). Clean architecture : a craftsman's guide to software structure and design дана формулеровка уже не много другая "A module should be responsible to one, and only one, actor." Именно actor, а не people.

Термин actor это больше по Event storming и архитектуру, а не про код.

Статья в вики по Event storming (https://en.wikipedia.org/wiki/Event_storming) предлагает три типа actor это Guest, Registered user, Administrator. Для того чтоб знать что у нас за actor нужно прогнать его через аутентификацию и авторизацию. Только после этого мы сможем понять кто это и какой ему отведен класс. А как применять SRP к аутентификации и авторизации, когда мы еще не знаем что это за пользователь. Или к классам где нет пользователя. Как применить к классу для нотификации. Мы же не будем email и sms пихать в один класс. Для таких случаев подойдет старый, добрый принцип описанный в этой статье и заботливо переведенный автором. Который почемуто многое считают не правельным или устаревшим.

Привет. Спасибо за комментарий. Спасибо что подсветили этот момент. Я в статье действительно не касался более позднего определения. Лайк за то, что комментарий подкреплен ссылками на пруфы.

Шел 2023 год, а в статьях до сих пор используют неверное определение и объяснение принципа Single Responsibility

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

Отталкиваясь от этого определения разработчику проще сделать второй шаг. В котором ему важно предположить, какие куски системы могут измениться, и заранее подложить соломы. И именно в таком виде (старое определение) + (понимание какие куски системы могут измениться) позволяет ему сразу писать внятный код.

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

Второе определение, которое дано Робертом Мартином в 2014 тоже очень хорошее. Но оно не столько про код сколько про архитектуру.

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

Это определение скорее для сеньоров и архитекторов. Термин actor это тоже скорее про Event Storming. На этапе Event Storming участвуют разработчики, которые очень хорошо знают SOLID и умеют его применять. Вряд ли они зайдут почитать эту статью.

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

Первая версия появилась когда:

  1. Не так давно был совершен переход от двухуровневой архитектуры к трехуровневой (многоуровневой).

  2. Не большие монолиты способны решать любые задачи.

  3. В по-настоящему больших проектах внедряют распределенную архитектуру и сервис-ориентированную архитектуру (SOA) в частности.

А когда появилась вторая версия:

  1. Абсолютно все проекты значительно выросли.

  2. Распределенная архитектура стала микросервисной и асинхронной.

  3. На многих проектах микросервисы вытеснили монолиты.

  4. Во многих проектах проектирование от DDD. В по-настоящему больших проектах проектирование от DDD стандарт.

Как следствие сильно поменялся подход к проектированию ПО в целом. И Роберт Мартин отразил это в новой трактовке старого принципа. Но это не значит, что старое определение не верное. Они скорее дополняют друг друга.

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

Вот основные причины, по которым я не сослался на позднее определение и статью от 2014 года.

Привет.

Для получения дополнительной информации смотрите the article Principles of Object Oriented Design by Robert C. Martin статью Роберта К. Мартина <ред: ссылка для меня не работает к сожалению>.

Попробуй эту ссылку http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

Считаю, что принцип должен прост, понятен и легкозапоминаем, как DRY и KISS.

Ну SOLID прост, понятен и легко запоминаем, как DRY и KISS.

95% разработчиков через полгода после последнего собеседования не вспомнят, что это вообще за буквы: OCP, SSP, SDP и прочие.

Если в команде есть ревью ему напомнят. К тому же разработчика могут попросить провести собеседование для кандидата.

Привет. Давай по порядку.

Зачем тут нужен помощник? есть 150 способов указать метакласс в качестве параметров класса, зачем нужен именно помощник?

Я описал абстрактный класс так как рекомендует документация. В исходный код питона его добавил Андрей Светлов в коммите от 14.12.2012. По моему класс помощник доступен с версии 3.5. До этого абстрактный класс строился при помощи конструкции metaclass=ABCMeta.

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

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

Исходный код python, абстрактные классы. Класс помошник. (https://github.com/python/cpython/blob/9cdf05bc28c5cd8b000b9541a907028819b3d63e/Lib/abc.py#L184)

Куча слов - и ни одного упоминания назначения принципов.

Я видел этот комментарий я не знаю что на него ответить. Надо задать вопрос более конкретно.

Как-то у вас получается что вы применили SOLID и поэтому у вас все хорошо, но непонятно что было плохо до его применения! Может и без SOLID у вас все было хорошо?

Ты прав, без SOLID у меня было все хорошо. Многие проекты на первых этапах своего развития отлично обходятся без него.

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

Я построил объяснение принципа отталкиваясь именно от такого примера.

Привет. Спасибо за коментарий.

По-моему эта статья лишь демонстрирует, что питон непригоден для программирования больших вещей.

Если бы ты написал это лет 10 назад я бы, скорее всего, с тобой согласился. Но сейчас Python-у ничего не нужно доказывать. Как будет через 10 лет я не знаю.

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

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

опять проблемы питона, которые не являются реально проблемами программирования в целом

Тут категорически не согласен. У меня не один сервис, использующий аутентификацию. Аутентификация попадает в сервис через зависимости библиотек. И деплоят сервисы не пачками по несколько сервисов, а по одному. Деплои могут запускаться независимо разными инициаторами.

Здесь скорее задеты проблемы микросервисоной архитектуры. Мы не можем закрыть доступ из сервисов в AD на уровне инфраструктуры пока у меня живут сервисы которые туда ходят. Мы деплоим их постепенно. Такие проблемы не завязаны на языке.

в этом пункте в статье я вообще не увидел самого принципа подстановки Лисков, что это такое и почему. "Вот есть интерфейсы, они могут быть разные, всё такое", казалось бы, причём здесь это

Тут я могу ошибаться, поправь меня если что. Вот определение с вики.

Принцип подстановки Лисков — это принцип организации подтипов в объектно-ориентированном программировании, предложенный Барбарой Лисков в 1987 году: если q(x) является свойством, верным относительно объектов x некоторого типа T, тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

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

Таким образом, идея Лисков о «подтипе» даёт определение понятия замещения — если S является подтипом T, тогда объекты типа T в программе могут быть замещены объектами типа S без каких-либо изменений желательных свойств этой программы.

Из этого самым важным я считаю «объекты типа T в программе могут быть замещены объектами типа S без каких-либо изменений». Это главное. Это возможно при соблюдении интерфейсов и сохранении сигнатур методов. А что от чего наследуется и каким образом мы проверяем что интерфейсы совпали это вторично. Для меня проблема, когда разработчики наследуются и в новом классе незначительно меняют интерфейс или сигнатуры. И даже если сейчас это не аффектит но через год другой может выстрелить в ногу. В таком случае проверка типов ничем не поможет. Более того в Python в типизации можно указывать не родительский тип, а список из дочерних. Более того, для соблюдения этого условия, мне даже не нужно наследование. Если класс T и класс S реализуют один интерфейс с моей точки зрения они уже соответствуют принципу подстановки Лисков потому что мы можем объект T заменить на объект S и наоборот. А в коде для проверки типов линтером я укажу список типов либо родительский тип если он есть. В Python какой способ проверки типов из них выбрать это преднамеренный выбор программиста.

Если у тебя MVP. Если у тебя все завизано на одной роли, нет никакого переиспользования пермишинов и бизнес говорит что все зашибись больше ничего не надо. Если AuthUserAD используется только в одном сервисе. То все отлично уживается в AuthUserAD.

Но аутентификация и авторизация это разные зоны ответственности. Аутентификация у нас используется одна на нескольких сервисах. А авторизация, что кому можно, на разных сервисах имеет свои отличия.

Надеюсь я ответил на твой вопрос.

Спсибо за такой развернутый коментарий. Да, действительно, у меня нигде по тексту нету рекомендаций по размещению абстрактного класса.

Привет. Спасибо за комментарий. Ну смотри, по тексту. Порядок подачи информации у меня не такой как обычно пишут про SOLID. Написано так, потому что я отталкиваюсь не от принципов, а от кода. В идеальном мире я начинаю писать код с абстрактных классов. Значит и повествование я начинаю с одного абстрактного класса и одного зависимого продового. В этот момент кода недостаточно для всех примеров про инверсию. Поэтому в том месте, где ты это ожидаешь все про инверсию этого нет.
Чуть ниже, когда принципе открытости и закрытости добавили дополнительный класс. А в принципе подстановки Лисков я разбирал разные виды зависимостей и некоторые особенности реализации в Python.
В принципе единой ответственности есть примеры с множественными зависимостями абстрактных классов для тонких и толстых интерфейсов.

Специально полазил по словарям. Гугл предлагает устарел, яндекс переводит как осуждаемый. Родной с детства Англо-русский словарь Мюллера переводит как 1) обесценивать(ся), падать в цене 2) унижать, умалять, недооценивать. А лингво в вариантах хужожественного перевода еще больше вариантов предлагает. Сам в шоке.

Спасибо за коментарий.

Считать ли это зависимостью на абстракциях?

Я думаю что нет. Здесь, как в ссылке написано, объект sequence должен реализовывать методы iter и next. Тогда он будет соответствовать интерфейсу и его можно здесь применить.

Нет, если наследник тоже абстрактный класс.

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

Я разный код видел.

Очень смешно. Особенно когда свой смотришь годовалый. :)

Ты поднял очень важную для меня тему. Когда я отдавал статью на рецензию то рецензенты сеньоры писали разные замечание, а рецензенты уровня джун+ все написали, что примеры лучше сделать более конкретные (чтоб их можно было закинуть в IDE и выполнить). Я думал над тем, как это сделать. Отказался от примеров на кошечках и рыбках. Классы аутентификации из примеров — это реальные классы, которые крутятся на проде. В методах этих классов обычно простой реквест в сервис аутентификации, обработка статус кода и сохранение их в экземпляре класса. С кодом запроса в api все хорошо справляются. Код с запросом скорее всего будет не интересен. Но при этом увеличит и так большие примеры.

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

Когда меня спрашивали про SOLID обычно обходилось без кода.

Разные источники предлагают разные переводы. В таком варианте будет даже лучше.

А без исключений он гарантированно закроет файл?

В принципе добавить можно. Но в С++ мой уровень компетенции ниже чем в Python. Поэтому стараюсь добавлять только то что верефицировано.

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

Как работают операторы try

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

• Если исключение происходит во время выполнения операторов блока try и оно соответствует одному из перечисленных в операторе, тогда интерпретатор Python переходит обратно на try и запускает операторы под первой конструкцией except, дающей совпадение со сгенерированным исключением. Затем объект сгенерированного исключения присваивается переменной, указанной после ключевого слова as в конструкции (при его наличии). После выполнения блока except поток управления возобновляется ниже полного оператора try (если только сам блок except не сгенерирует еще одно исключение, в случае чего процесс начинается заново с этой точки в коде).

5 издание том 2 Часть VII глава 34 стр 328

Обратите внимание, что в Python отсутствует способ возвратиться обратно к коду, который сгенерировал исключение (конечно, не считая повторного запуска кода, достигнувшего данной точки). Как только вы перехватили исключение, поток управления продолжается после полного оператора try, перехватившего исключение, но не после оператора, его инициировавшего. На самом деле Python очищает память от любых функций, которые завершили работу в результате возникновения исключения, подобных функции fetcher в нашем примере; они не возобновляемы. Оператор try перехватывает исключения и является тем местом, где программа возобновляет выполнение.

5 издание том 2 Часть VII глава 33 стр 320

Однако подлинный возврат к предыдущему состоянию не является частью языка Python. Возврат к предыдущему состоянию перед переходом отменяет все вычисления, но исключения Python этого не делают: переменные, которым присваивались значения между моментом входа в оператор try и моментом генерации исключения, не переустанавливаются в свои предыдущие значения. Даже генераторные функции и выражения, обсуждаемые в главе 20 первого тома, не делают полный возврат к предыдущему состоянию — они реагируют на запросы next(G) просто восстановлением состояния и возобновлением выполнения.

5 издание том 2 Часть VII глава 33 стр 318

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

Information

Rating
Does not participate
Works in
Registered
Activity

Specialization

Backend Developer