Чистая архитектура. Искусство разработки программного обеспечения - страница 23

стр.

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

Признак 1: непреднамеренное дублирование

Мой любимый пример – класс Employee из приложения платежной ведомости. Он имеет три метода: >calculatePay(), >reportHours() и >save() (рис. 7.1).


Рис. 7.1. Класс Employee


Этот класс нарушает принцип единственной ответственности, потому что три его метода отвечают за три разных актора.

• Реализация метода >calculatePay() определяется бухгалтерией.

• Реализация метода >reportHours() определяется и используется отделом по работе с персоналом.

• Реализация метода >save() определяется администраторами баз данных.

Поместив исходный код этих трех методов в общий класс >Employee, разработчики объединили перечисленных акторов. В результате такого объединения действия сотрудников бухгалтерии могут затронуть что-то, что требуется сотрудникам отдела по работе с персоналом.

Например, представьте, что функции >calculatePay() и >reportHours() используют общий алгоритм расчета не сверхурочных часов. Представьте также, что разработчики, старающиеся не дублировать код, поместили реализацию этого алгоритма в функцию с именем >regularHours() (рис. 7.2).


Рис. 7.2. Общий алгоритм


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

Разработчик, которому было поручено внести изменение, заметил, что функция >regularHours() вызывается методом >calculatePay(), но, к сожалению, не заметил, что она также вызывается методом >reportHours().

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

Разумеется, сотрудники отдела по работе с персоналом не знали о произошедшем и продолжали использовать отчеты, генерируемые функцией >reportHours(), но теперь содержащие неправильные цифры. В какой-то момент проблема вскрылась, и сотрудники отдела по работе с персоналом разом побледнели от ужаса, потому что ошибочные данные обошлись их бюджету в несколько миллионов долларов.

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

Признак 2: слияния

Слияния – обычное дело для исходных файлов с большим количеством разных методов. Эта ситуация особенно вероятна, если эти методы отвечают за разных акторов.

Например, представим, что коллектив администраторов баз данных решил внести простое исправление в схему таблицы >Employee. Представим также, что сотрудники отдела по работе с персоналом пожелали немного изменить формат отчета, возвращаемого функцией >reportHours().

Два разных разработчика, возможно, из двух разных команд, извлекли класс >Employee из репозитория и внесли изменения. К сожалению, их изменения оказались несовместимыми. В результате потребовалось выполнить слияние.

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

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

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

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

Решения

Существует много решений этой проблемы. Но каждое связано с перемещением функций в разные классы.

Наиболее очевидным, пожалуй, является решение, связанное с отделением данных от функций. Три класса, как показано на рис. 7.3, используют общие данные