Comments 21
Самое главное, правильное и очевидное сказано в начале:
Как подсказывает мой опыт, нужно не следовать этим принципам слепо, а обдумывать их, понимать и определять, где они могут дать добавленную ценность.
Остальное - нераскрытый потенциал в узких контекстах.
Пример из моего опыта. На одной из прошлых работ переписал немаленький проект в соответствии с SOLID с примесью SOA и AOP. Коллеги не сразу въехали в то, что получилось. Надо было показать, как и почему это работает. Окончательное принятие пришло, когда коллеги распробовали две выгоды: сделать за пару часов то, что раньше занимало пару-тройку дней, и сделать исправление пары строк кода, не боясь, что это исправление надо делать ещё в кучке потаённых мест или что всё развалится на проде. А заодно в проекте появились юнит и автотесты.
сделать за пару часов то, что раньше занимало пару-тройку дней, и сделать исправление пары строк кода, не боясь, что это исправление надо делать ещё в кучке потаённых мест или что всё развалится на проде.
В этом заслуга SOLID или автотестов?
TDD - в первую очередь про дизайн и уже потом про автотесты.
И того и другого.
SOLID, если его правильно готовить, приводит к тому, что нечто пишется в коде один раз. Если тебе надо это доработать - то тут и дорабатываешь, притом с осознаваемыми последствиями.
Автоматизированные тесты (самые разные) позволяют раньше отловить вторичные последствия, в частности, из-за ошибок реализации доработок. А если уж количество и качество тестов близки к идее TDD, то можно смело делать рефакторинги, определяя необходимые доработки по свалившимся тестам.
Но, факт, при нормальной архитектуре кода хорошие автотесты писать намного проще. В мною переписанном проекте большая часть юнит-тестов выглядели как куски бизнес-сценариев (местами полные, например, имитировали обработку REST-запроса на создание заказа, где целью теста было убедиться, что из входных данных в итоге формируется правильный набор объектов), что хорошо работало благодаря заглушкам внешних интерфейсов (БД, файлы, внешние апи), внедрённых в DI-контейнер. В итоге, при не очень большом количестве тестов тестовое покрытие было 96%.
Поскольку перевод опубликовало именно издательство, не могу удержаться от вопроса. А чем в данном случае разница между "правило" и "гайдлайн"? В моем понимании rule(правило) и guideline(указание, руководство) это синонимы.
Google переводит "guidelines" как "методические рекомендации", а для "rules" предлагает подсказку "rules and regulations", где "regulations" – "нормативные документы".
Видимо, в этом и разница.
Один мужик сказал в коворкинге "Тот случай дал мне ценный опыт" вместо "Тот кейс дал мне ценный экспириенс" и его тут же осмеяли, облили смузи и перевели в чуханы.
Проблема подобных статей в том, что они берут высосанные из пальца примеры на десять строчек, где никакой SOLID особо не нужен, и торжественно доказывают, что он тут особо не нужен. Это все равно что сказать "грузовики не нужны, все мои пожитки можно перевезти на легковушке".
SOLID нужен, чтобы не потонуть, когда счет пошел на десятки тысяч строк кода. Поэтому по-чесноку примеры нужно показывать на больших системах. И сравнивать например такую метрику, как время обнаружения бага. Или время на погружение в задачу для сотрудника любой квалификации. Сравнение гарантированно будет в пользу системы, где правила соблюдают.
Проблема подобных статей
Напомнило статьи про сравнение Vue/React/Angular.
Люди делают To-do листы (которые лучше всего уже на ваниле делать), сравнивают бенчмарки, делятся опытом... А ведь инструмент познается в работе. Сделайте высоконагруженный портал на 100к человек и поддерживайте его три года. Потом сделайте тоже самое, но на другом фреймворке и по новой. Тогда можно будет адекватно сравнить "за" и "против". Лёгкость внедрения тестов, онбординга, поиск сотрудников, инструментарий, комьюнити, крайние случаи, экосистема. Там 1000 и 1 деталь.
Типичное объяснение этого принципа [SRP] – «должна существовать всего одна причина, по которой может быть изменен данный класс».
Скорее, здесь имеется в виду "должен существовать всего один класс причин...". Другими словами, структурная модель ООП-классов должна соответствовать функциональной модели системы, разработку которой мы ведем.
Так, в примере из статьи, можно все причины, касающиеся результата (включая ведение истории), отнести к одному классу причин, а можно ведение истории результатов посчитать имеющим больше отношения к истории, чем к результату, -- в зависимости от того, насколько значимое место в функциональной модели нашей системы эта история занимает.
В другом примере из статьи класс, единственная ответственность которого "обеспечивать онлайн-бронирование номера в отеле", не будет нарушать SRP при условии, что он не выполняет сам отдельные операции, на которые декомпозируется онлайн-бронирование, а обращается для этого к модулям более низкого уровня (другими словами, организует функциональность более низкого уровня в решение этой конкретной задачи). Но в таком случае откуда у него возьмется 100k строк кода?
По-видимому, при определении ответственности класса стоит задаваться не вопросом "что делает класс?", а вопросом "зачем он это делает?" Так, если класс состоит из двух методов, возвращающего и сохраняющего цену, соответственно, то ответственность у него, скорее всего, одна -- обеспечивать персистентность данных. Но если функциональная модель системы подразумевает сложное взаимодействие микросервисов, репликацию, шардирование и тому подобное и/или сохранением данных занимается одна часть системы, а получением -- другая, то и ответственности будет две.
Обычно его [принцип открытости/закрытости] объясняют так: «класс должен быть закрыт для модификации, но открыт для расширения». Это означает, что поведение класса следует менять не на уровне его исходного кода, а путем расширения.
Скорее, это означает, что классы следует проектировать так, чтобы за счёт их комбинирования охватить максимальное поле возможностей. Чтобы единственной причиной для изменения поведения было изменение требований со стороны зоны ответственности этого класса, а новую функциональность можно было бы добавить без тюнинга старой, лишь за счет надстройки и рекомбинации.
Однажды мне стало любопытно откуда вообще взялся этот SOLID с которым все так носятся, более 20 лет показывая друг-дружке примеры "хорошего кода" в десяток строк, и каждый раз срывая овации. Код, который на практике до сих пор в глаза ни кто не видел. Зачем это проходят в институтах и на кой ляд потом спрашивают на каждом собеседовании? Почему хороших принципов ровно 5, а не 3 или там 42?
Оказывается все просто. Как-то сидели пацаны в своем доисторическом чатике и спорили кто придумает 10 заповедей для ООП (вот прямо так и назвали топик "The Ten Commandments of OO Programming"). Потому, что раньше кто-то уже предлагал около 20 хороших идей. Но топикстартер предложил урезать до 10 (логика железная, прямо как в скрам покере, как будто у него бюджет на хорошие идеи).
В общем, кому интересно посмотреть на английские буквы, добро пожаловать в 1995 год: https://groups.google.com/g/comp.object/c/WICPDcXAMG8
Меня давно терзает один вопрос: Все знают SOLID и прочие умные аббревиатуры, все их спрашивают друг у друга на собеседованиях, пересказывают с умным лицом на конференциях, читают вместо корпоративных псалмов хором на тренингах. А в большинстве репозиториев почему г...код без этого вот всего? Вспоминаю сразу Винни-Пуха - "У меня правильнописание хромает. Оно хорошее, но почему-то хромает".
Пример кода, приведенный как иллюстрация "явности"
public class Foo{
public bool IsValid(int id){
if(id > 10000)
return false;
return true;
}
}
Точно может быть более явным )
public class Foo{
public bool IsValid(int id) {
return id <= 10000;
}
}
Общий посыл статьи мне нравится, но подход "извратить изначальное понимание, напилить странных примеров на основе этого извращенного понимания, а затем доблестно это всё побороть" уже несколько приелся
Как было сказано выше, интерфейс ни на йоту не меняет ситуацию со связанностью кода.
Смотря связанность какого модуля имеется ввиду. Если имеется ввиду связанность модуля верхнего уровня, то мы избавляемся от зависимости, а значит уменьшаем кол-во связей (чуть подробнее написал далее)
В исходном случае DIP, мы зависим от модуля нижнего уровня. Применяя инверсию зависимости, путем добавления интерфейса в модуль (ВАЖНО)верхнего уровня (т.е. интерфейс - это часть модуля верхнего уровня), модуль верхнего уровня теперь зависит только от самого себя, т.е. от интерфейса, которого мы ему добавили.
Здесь введение интерфейса ничего не меняет в степени связности между Foo и Bar: если мы изменим ожидаемый строковый формат в Bar, то нам придется внести изменения и в Foo.
Мы не можем просто так менять Bar (модуль нижнего уровня) и подстраивать под него модуль верхнего уровня Foo. Модули нижних уровней должны подстраиваться под модули верхних уровней, а не наоборот.
Но, увы, этот подход «ограничен по масштабированию» и начиная с некоторого уровня сложности проекта приходится действовать по армейскому принципу «пусть безобразно, зато единообразно» — иначе все просто-напросто расползется, пойдет что — в лес, что — по дрова, и проект просто не получится довести до работоспособного состояния.
И вот тут правильные догмы окажутся очень к месту — чтобы это самое единообразие внести так, чтобы не было совсем уж безобразно. Впрочем, необходимость здравого смысла (и опыта!) для их успешного использовния это не отменяет.
Как-то так.
PS А вот перевод заголовка — никуда не годный.
Uncle Bob все эти примеры уж столько раз обговаривал, что непонятно, зачем очередная статья
Ну и
SRP означает «Принцип единственной ответственности» - нет, это не совсем так. UB обьясняет его как "A class should have one, and only one, reason to change". Проще говоря если у вас есть бизнес-правило (business rule), то оно должно описываться одним модулем, и наоборот, модуль должен описывать только одно бизнес-правило
И тогда OCP ему никак не противоречит - если вы расширяете бизнес правило, вы не добавляете левый функционал в модуль - он все еще относится к текущему
Сюда же плавно подтягивается лисков принцип - если ваша иерахия описывает одно бизнес-правило с расширениями, любой конкретный класс за интерфейсом будет исполнять это бизнес-правило, расширенно или нет
Сюда же и ISP - интефейс описывает одно бизнес-правило, а не несколько. Правило, понятно, применимо там, где нужно уходить в интерфейсные абстракции, а если их нет, то смысл о нем упоминать? И тем более приводить как пример ненужности
Ну и т.п.
В солиде нету никакой мистики, просто его почему-то каждый раз понимают как-то либо чересчур примитивно, либо чересчур абстрактно
Кодекс - это просто свод указаний, а не жёстких законов. Добро пожаловать к нам на борт, мисс Тёрнер.
SOLID – это не правила, а гайдлайны