В области программирования SOLID это как три кита и десять заповедей одновременно. Все программисты должны знать про их существование. Все должны придерживаться этих принципов. Должны. А вот придерживаются ли? Ответить на этот вопрос не так просто как кажется. Люди могут с чистой совестью ответить «придерживаюсь» и творить полную дичь просто слабо понимая суть принципа. Вот о примерах такого понимания я сейчас и поведу речь.
Принцип 1. SRP. Принцип единственной ответственности. Цитата:
принцип ООП, обозначающий, что каждый объект должен иметь одну ответственность и эта ответственность должна быть полностью инкапсулирована в класс. Все его поведения должны быть направлены исключительно на обеспечение этой ответственности.
Давайте вот тут и остановимся. Дальше лезть интересно и полезно, но мой опыт показывает что многие программисты во время собеседования выдернули это определение и поняли в рамках своего восприятия мира. Если прочитать учебник чуть дальше, то можно узнать, когда принцип применять смысл имеет, а когда – нет. На моей практике постоянно встречаю применение данного принципа как написание метода, который используется ровно 1 раз в одном сценарии. Например, мы создаем заявку в тех.поддержку. Вот у нас на каждый тип заявки будет отдельный метод бизнес-логики для обработки. Не generic, не шаблон. А именно предельно огороженный метод, который второй раз применяться не будет. Даже имя уникально. Нас не остановит частичное копирование логики. Там же есть изменения? Есть. Значит не копирование. То что изменения потребуют облазить весь проект – проблема того кто эти изменения будет вносить. Зато у метода единственная ответственность.
Принцип 2. OCP. Принцип открытости/закрытости. Цитата:
Принцип открытости/закрытости означает, что программные сущности должны быть:
· открыты для расширения: означает, что поведение сущности может быть расширено путём создания новых типов сущностей.
· закрыты для изменения: в результате расширения поведения сущности, не должны вноситься изменения в код, который эта сущность использует.
Из пояснений становится понятно что нарушения данного принципа связаны с нарушением первого принципа. А значит и дурное понимание первого принципа влечет психоделику в понимание данного принципа. Раз у нас есть 2 метода, каждый из которых живет своей жизнью, значит по принципу закрытости их можно каждый свободно дорабатывать, а также значит что доработки одного метода не сломают второй. Логично? Да, логично. Только это справедливо исключительно для этапа сборки, компиляции и прогона тестов. С ними все будет в порядке. А вот касательно бизнес-сценариев наступает «туман войны». Если добавляется проверка прав доступа – должна ли она добавляться во второй метод? 100% Да? Эммм, нет. Зависит от реализации прав доступа. Есть еще фильтрация по специфическим флагам, которые для первого метода будут оправданы, а для второго – с гарантией нет. В итоге время доработки мы умножаем еще на 2 для разбора необходимости внесения изменений.
Принцип 3. LSP. Принцип подстановки Барбары Лисков.
Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом.
Звучит просто, нет? Задаем базовый класс, в наследниках уточняем поведение, а в интерфейсы методов передаем исключительно базовый тип. Я сталкивался с двумя сюрпризами.
Сюрприз номер 1 – базовый класс не знает, что он базовый и никак не сообщает об этом, а, следовательно, программист может не сразу заметить что поведение зависит от фактической реализации.
Сюрприз номер 2 – специфика синтаксиса языка C# допускает различные методы переопределения. И когда внутри одного класса используется одновременно и классическое прямое переопределение и перекрытие – хочется достать большой тесак и пойти поискать автора.
По идее обе эти проблемы можно решить покрытием unit-тестами. Но кто их пишет? Зачем время тратить… лучше еще одну задачу реализовать, для бизнеса это полезнее.
Принцип 4. ISP. Принцип разделения интерфейса. Цитата:
Программные сущности не должны зависеть от методов, которые они не используют. Принцип разделения интерфейсов говорит о том, что слишком «толстые» интерфейсы необходимо разделять на более маленькие и специфические, чтобы программные сущности маленьких интерфейсов знали только о методах, которые необходимы им в работе. В итоге, при изменении метода интерфейса не должны меняться программные сущности, которые этот метод не используют.
Снова – куда проще? Но мы ведь помним. У нас каждый метод имеет «единственную ответственность». Представим, что для исключения лишних зависимостей мы новый метод перенесем в новый класс. Проходит пара месяцев, по текучке – 3 человека пришли, 2 ушли. И вот человеку нужно «использовать существующий метод». Какой? Сам должен найти? Ну да, только исходов тут 3. 1) Человек найдет правильный метод, 2) Человек найдет неправильный метод, 3) Человек напишет третий метод. Т.е. имеем два шанса из трех пойти по кривой дорожке.
Принцип 5. DIP. Принцип инверсии зависимостей. Цитата:
· Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
· Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
В моем опыте тут проще всего - коллеги бояться абстракций как огня. Почему? Так выше все описано. Если на весь описанный трындец снаружи еще присыпать абстракций – жизнь превратится в ад окончательно. Вот последний рубеж, перейдя который можно окончательно сойти с ума. Или первопричина остального? Вопрос интересный, но ответа у меня нет.
Итог? Никто из нас не безгрешен. Тот, кто считает, что никогда в жизни не писал гумнокода – пусть первый бросит в меня камень. Что делать? Ну как всегда – учиться, учиться и еще раз учиться. А заодно не бояться признаться что ваш код – хрень даже если работает и кажется хорошим.