Принципы SOLID. Или как сделать код SOLID-нее.
На мой взгляд чистота кода - ключевой аспект в хард-навыках программиста-разработчика. Даже не столько знание алгоритмов решает (хотя и это очень важный момент), а именно умение писать понятный чистый код. Это как с человеком. Если человек, утром вставая с постели, имеет привычку заправлять ее после сна, всегда имеет в распоряжении чистую не мятую одежду, следит за своим внешним видом, не позволяя себе ходить с грязной головой или не чищенными зубами, грамотно излагает свою речь, то, скорее всего, вы предпочтете иметь дело с таким человеком. По крайней мере, вам будет приятно с ним общаться, не взирая на его физические данные и политические взгляды. Точно так и с кодом.
Дело в том, что в реальных условиях продакшена очень важным является возможность дальнейшей поддержки ПО и расширения функционала. Но как добиться того, чтобы тебе самому было понятно, что ты накодил полгода назад?
В различных сферах производства (да и вообще человеческой деятельности в целом) для однозначного понимания и четкого соответствия выходного продукта к поставленным задачам, приняты различные стандарты (ГОСТ-ы). Строители стоят по своим стандартам, механики собирают автомобили по своим сводам нормативов и допусков. Даже композиторы пишут музыку по определенным правилам, и то, что написал композитор, будет однозначно понятно всем остальным музыкантам.
И тут мы возвращаемся к вопросу о написании кода:) Все дело в том, что в данной сфере нет определенных ГОСТ-ов и четко прописанных правил. Однако, в программировании существуют некоторые договоренности по тому, как надо организовывать код при его написании. Это не стандарт, а скорее соблюдение рекомендаций и общих условностей. Это, как правила хорошего тона и проявление хороших манер в обществе. Так вот, одним из самых часто используемых в программировании подобных проявлений «хороших манер и хорошего тона» является свод принципов SOLID (конечно данный свод принципов не единственный, существует еще множество подобных смысловых аббревиатур, например KISS и DRY).
SOLID - это не просто перечень правил, а скорее список концепций, понимая и соблюдая которые, при проектировании и написании кода, мы можем иметь возможность поддерживать свой код и расширять его со временем новым функционалом. Такой код называется условно чистым (хотя о чистоте можно спорить очень долго) и поддерживаемым. Иначе, мы будем вынуждены прибегать к постоянному переписыванию (модернизации) уже существующих частей нашей программной системы, что приводит к экспоненциальному росту багов и такой же сложности по их устранению. Иными словами, SOLID это свод принципов, который все разработчики должны хорошо понимать, чтобы избегать создания плохой архитектуры.
Принципы SOLID изначально были описаны Робертом Мартином (автором знаменитой книги «Чистый код»). Он описал пять принципов, соблюдая которые, мы можем уйти от создания непонятного, трудно поддерживаемого запутанного кода и, как следствие, соблюдения данных принципов, создавать более качественное ПО.
Так что же такое SOLID?
Вот как расшифровывается аббревиатура SOLID:
- S: Single Responsibility Principle (Принцип единственной ответственности).
- O: Open-Closed Principle (Принцип открытости-закрытости).
- L: Liskov Substitution Principle (Принцип подстановки Барбары Лисков).
- I: Interface Segregation Principle (Принцип разделения интерфейсов).
- D: Dependency Inversion Principle (Принцип инверсии зависимостей).
Давайте рассмотрим данные принципы. Конечно, очень глубоко копать каждый их них, в рамках этой статьи, я не стану. Постараюсь описать тезисно, только основную суть.
Принцип единственной ответственности (SRP)
Класс должен быть ответственен за что-то одно. Методы и свойства класса должны описывать одну определенную семантику и служить одной цели.
Если класс отвечает за решение нескольких задач, его подсистемы, реализующие решение этих задач, оказываются связанными друг с другом. Изменения в одной такой подсистеме ведут к изменениям в другой.
Другими словами, если класс имеет несколько назначений, его нужно разделить на отдельные классы.
Принцип открытости-закрытости (OCP)
Программные сущности (классы, модули, функции) должны быть открыты для расширения (масштабирования), но закрыты для модификации.
Например, для добавления (расширения) функционала код должен позволять нам лишь масштабировать необходимую сущность, (применяя, например, наследование) в которой мы описываем (реализуем) новый функционал, который при помощи полиморфизма будет вызван именно для объекта той сущности, которой соответствует.
Принцип подстановки Барбары Лисков (LSP)
Данный принцип тесно связан с механизмом наследования из ООП, и я уже описывал его в своей статье, посвященной ООП из серии «Вселенная программирования».
Цель этого принципа заключаются в том, чтобы классы-наследники могли быть использованы вместо родительских классов, от которых они образованы, не нарушая работу программы.
Вообще, если наследование как отношение (is-a) используется в проекте, оно обязательно должно соответствовать данному принципу. Иначе, лучше всего использовать отношение типа композиция (has-a).
Принцип разделения интерфейсов (ISP)
Создавайте узкоспециализированные интерфейсы, предназначенные для конкретного клиента. Клиенты не должны зависеть от интерфейсов, которые они не используют.
Это означает, что нужно разбивать интерфейсы на более мелкие, лучше удовлетворяющие конкретным потребностям клиентов (сущностям, которые реализуют интерфейс).
По смыслу очень похоже на принцип единственной ответственности SRP. Цель принципа ISP заключается в минимизации побочных эффектов и повторов за счёт разделения общий задачи абстракции на независимые узкоспециализированные интерфейсы, каждый из которых должен решать лишь какую-то одну задачу (в этом он похож на принцип SRP), поэтому всё, что выходит за рамки этой задачи, должно быть вынесено в другой интерфейс, чтобы клиент реализовывал только те методы базовой абстракции, которые он использует согласно соответствию своей семантики.
Принцип инверсии зависимостей (DIP)
Общая формулировка гласит: объектом зависимости должна быть абстракция, а не конкретная реализация. И далее мы имеем два пункта:
- Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей реализации. Детали реализации должны зависеть от абстракций.
Объяснить максимально быстро и просто данный принцип достаточно сложно:) Суть в том, что при описании класса (или модуля) Автомобиль
, который явно использует в своем конструкторе и, соответственно, зависит от более низкоуровневого класса Двигатель
. Если мы изменим логику работы и сигнатуру класса Двигатель
(теперь стал ЭлектроДвигатель
), нам придется править и класс Автомобиль
. Тем самым принцип DIP нарушается.
Чтобы все соответствовало, необходимо сделать зависимость Автомобиля от абстракции двигателя. Класс Автомобиль
будет знать лишь об абстракции (интерфейсе) Двигатель
, который может быть реализован как угодно, и не вносить никакого влияния на более высокоуровневый класс Автомобиль
.
Таким образом, мы (в первом приближении) рассмотрели все пять принципов SOLID, которым следует придерживаться разработчику. Но, что значит соблюдать данные принципы? Просто распечатать эти правила, повесить на стенку за монитором и смотреть в них все время? Нет, принципы SOLID должны стать образом мышления, своего рода, философией написания кода. Программист должен размышлять в терминах SOLID, при написании своего кода. И только тогда этот код станет чистым и SOLID-ным:)