Страница 14 из 20
Каждая из этих парадигм что-то отнимает у нас. Каждая ограничивает подходы к написанию исходного кода. Ни одна не добавляет новых возможностей.
Фактически последние полвека мы учились тому, как не надо делать.
Осознав это, мы должны признать неприятный факт: разработка программного обеспечения не является быстро развивающейся индустрией. Правила остаются теми же, какими они были в 1946 году, когда Алан Тьюринг написал первый код, который мог выполнить электронный компьютер. Инструменты изменились, аппаратура изменилась, но суть программного обеспечения осталась прежней.
Программное обеспечение – материал для компьютерных программ – состоит из последовательностей, выбора, итераций и косвенности. Ни больше ни меньше.
III. Принципы дизайна
Хорошая программная система начинается с чистого кода. С одной стороны, если здание строить из плохих кирпичей, его архитектура не имеет большого значения. С другой стороны, плохие кирпичи можно перемешать с хорошими. Именно на этом основаны принципы SOLID.
Принципы SOLID определяют, как объединять функции и структуры данных в классы и как эти классы должны сочетаться друг с другом. Использование слова «класс» не означает, что эти принципы применимы только к объектно-ориентированному программному коду. В данном случае «класс» означает лишь инструмент объединения функций и данных в группы. Любая программная система имеет такие объединения, как бы они ни назывались, «класс» или как-то еще. Принципы SOLID применяются к этим объединениям.
Цель принципов – создать программные структуры среднего уровня, которые:
• терпимы к изменениям;
• просты и понятны;
• образуют основу для компонентов, которые могут использоваться во многих программных системах.
Термин «средний уровень» отражает тот факт, что эти принципы применяются программистами на уровне модулей. Они применяются на уровне, лежащем непосредственно над уровнем программного кода, и помогают определять программные структуры, используемые в модулях и компонентах.
Как из хороших кирпичей можно сложить никуда не годную стену, так из хорошо продуманных компонентов среднего уровня можно создать никуда не годную систему. Поэтому сразу после знакомства с принципами SOLID мы перейдем к их аналогам в мире компонентов, а затем к высокоуровневым принципам создания архитектур.
Принципы SOLID имеют долгую историю. Я начал собирать их в конце 1980-х годов, обсуждая принципы проектирования программного обеспечения с другими пользователями USENET (ранняя разновидность Facebook). На протяжении многих лет принципы смещались и изменялись. Некоторые исчезали. Другие объединялись. А какие-то добавлялись. В окончательном виде они были сформулированы в начале 2000-х годов, хотя и в другом порядке, чем я представлял.
В 2004 году или около того Майкл Физерс прислал мне электронное письмо, в котором сообщил, что если переупорядочить мои принципы, из их первых букв можно составить слово SOLID[21] – так появились принципы SOLID.
Последующие главы подробнее описывают каждый принцип, а пока познакомьтесь с краткой аннотацией:
SRP: Single Responsibility Principle – принцип единственной ответственности.
Действительное следствие закона Конвея: лучшей является такая структура программной системы, которая формируется в основном под влиянием социальной структуры организации, использующей эту систему, поэтому каждый программный модуль имеет одну и только одну причину для изменения.
OCP: Open-Closed Principle – принцип открытости/закрытости.
Этот принцип был сформулирован Бертраном Мейером в 1980-х годах. Суть его сводится к следующему: простая для изменения система должна предусматривать простую возможность изменения ее поведения добавлением нового, но не изменением существующего кода.
LSP: Liskov Substitution Principle – принцип подстановки Барбары Лисков.
Определение подтипов Барбары Лисков известно с 1988 года. В двух словах, этот принцип утверждает, что для создания программных систем из взаимозаменяемых частей эти части должны соответствовать контракту, который позволяет заменять эти части друг другом.
ISP: Interface Segregation Principle – принцип разделения интерфейсов.
Этот принцип призывает разработчиков программного обеспечения избегать зависимости от всего, что не используется.
DIP: Dependency Inversion Principle – принцип инверсии зависимости.
Код, реализующий высокоуровневую политику, не должен зависеть от кода, реализующего низкоуровневые детали. Напротив, детали должны зависеть от политики.
Эти принципы детально описаны во множестве публикаций[22]. Последующие главы освещают влияние этих принципов на проектирование архитектур, не повторяя подробное обсуждение из этих публикаций. Если вы еще не знакомы с перечисленными принципами, обсуждения, следующего далее, будет недостаточно, чтобы понять их во всех подробностях, поэтому я рекомендую обратиться к документам, перечисленным в сноске.
7. Принцип единственной ответственности
Из всех принципов SOLID наиболее трудно понимаемым является принцип единственной ответственности (Single Responsibility Principle, SRP). Это, вероятно, обусловлено выбором названия, недостаточно точно соответствующего сути. Услышав это название, многие программисты решают: оно означает, что каждый модуль должен отвечать за что-то одно.
Самое интересное, что такой принцип действительно существует. Он гласит: функция должна делать что-то одно и только одно. Этот принцип мы используем, когда делим большие функции на меньшие, то есть на более низком уровне. Но он не является одним из принципов SOLID – это не принцип единственной ответственности.
Традиционно принцип единственной ответственности описывался так:
Модуль должен иметь одну и только одну причину для изменения.
Программное обеспечение изменяется для удовлетворения нужд пользователей и заинтересованных лиц. Пользователи и заинтересованные лица как раз и есть та самая «причина для изменения», о которой говорит принцип. Фактически принцип можно перефразировать так:
Модуль должен отвечать за одного и только за одного пользователя или заинтересованное лицо.
К сожалению, слова «пользователь» и «заинтересованное лицо» не совсем правильно использовать здесь, потому что одного и того же изменения системы могут желать несколько пользователей или заинтересованных лиц. Более правильным выглядит понятие группы, состоящей из одного или нескольких лиц, желающих данного изменения. Мы будем называть такие группы акторами (actor).
Соответственно, окончательная версия принципа единственной ответственности выглядит так:
Модуль должен отвечать за одного и только за одного актора.
Теперь определим, что означает слово «модуль». Самое простое определение – файл с исходным кодом. В большинстве случаев это определение можно принять. Однако некоторые языки среды разработки не используют исходные файлы для хранения кода. В таких случаях модуль – это просто связный набор функций и структур данных.
Слово «связный» подразумевает принцип единственной ответственности. Связность – это сила, которая связывает код, ответственный за единственного актора.
Пожалуй, лучший способ понять суть этого принципа – исследовать признаки его нарушения.
Признак 1: непреднамеренное дублирование
Мой любимый пример – класс Employee из приложения платежной ведомости. Он имеет три метода: calculatePay(), reportHours() и save() (рис. 7.1).
21
В данном случае слово «SOLID» можно перевести как «прочный», «надежный», «основательный». – Примеч. пер.
22
Например, Agile Software Development, Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall, 2002 (Роберт С. Мартин. Гибкая разработка программ на Java и C++: принципы, паттерны и методики. М.: Вильямс, 2017. – Примеч. пер.), http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod и https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) (можно просто выполнить поиск в Google по слову «SOLID»).