Solid

Раз уж вы решили нажать на этот загадочный заголовок, то, полагаю, вы уже знакомы с принципами SOLID. Хотя бы с самим сокращением. 

Здесь я покажу, как легко рефакторить мусорный код, и дам вам основу для улучшения качества кода. К счастью, писать SOLID’ный код  —  невероятно легко.

Давайте кое-что проясним, прежде чем погружаться в код. У меня нет возможности продемонстрировать все аспекты каждого принципа, так что я ограничусь несколькими практическими примерами. Дальше вам придется думать самостоятельно. Используйте то, что работает именно для вас. 

Принцип единой ответственности

Если при появлении новой функции нужно изменить класс, то перед вами  —  нарушение принципа единой ответственности. Как правило, это происходит, когда нужно реализовать сквозные задачи, такие как ведение логов, кэширование и т. д.

Итак, вот для примера фрагмент кода, где класс содержит слишком много ответственностей. Он обрабатывает как операции, связанные с репозиторием, так и ведение логов.

Класс, выполняющий ведение логов и операции с базой данных

Мы реорганизуем его, разделив на два отдельных класса. Один используется для декорирования другого. Обратите внимание: я также выделил интерфейс, и класс Decorator принимает экземпляр интерфейса в качестве зависимости. Вот как с лёгкостью декорировать класс.

Разбейте один класс на два

Видите: UserRepository стал намного проще, и теперь выполняет только одну задачу. А для ведения журнала мы создали отдельный класс UserRepositoryLoggingDecorator.

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

Не изменяйте существующие классы

Это мой самый любимый принцип. Возможность просто добавлять новые классы, не трогая существующие,  —  это чертовски здорово.
Нарушения этого принципа легко найти  —  они характеризуются массивным ветвлением if-else или switch, или где каждая новая функция требует нового if. Как, например, этот форматер продукта, который берет тип продукта и возвращает строку, отформатированную неким желаемым образом:

Плохо написанный метод форматирования

На первый взгляд все не так уж плохо. Код достаточно простой. Однако для каждого нового типа формата необходимо нарушить OCP, изменяя метод. Кроме того, если обнаружится ошибка логику придется исправлять прямо среди постороннего кода.

Применение полиморфизма для удаления безумных, ненужных if-else —  одно из лучших и самых простых решений. Таким образом, вы также соприкасаетесь с принципом подстановки Лисков.

Теперь исправим код выше. Не пожалейте времени и внимательно его изучите:

Рефакторинг в стратегические классы

Насколько больше стало кода! Рефакторинг  —  это не сведение кода к минимуму, а упрощение для лучшей обработки и понимания.

Во-первых, мы берем каждый логический фрагмент внутри if и else-if и помещаем его в свой собственный класс форматирования. У каждого класса теперь есть одна задача. Когда понадобится исправить ошибку, вы будете знать, что весь окружающий код связан с ней.

Во-вторых, каждый тип форматера регистрируется в ProductFormatter, вызывая RegisterFormatter().

Реализация Format() была изменена, чтобы через поиск обнаруживать, какой форматер нужно использовать, а не обрабатывать само форматирование.

Кроме того, обратите внимание, что теперь здесь есть форматер JSON. Мы просто добавили для него класс.

Вот пример того, как использовать форматер продукта:

Пример использования класса Formatter с зарегистрированными форматерами

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

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

Используйте только то, что имеет отношение к вашему классу

Возможно вы уже применяли или даже создавали свои собственные классы типа «швейцарский нож». Это, например, классы service и manager. Достаточно общие, чтобы вместить все, что угодно. По сути, это логические мусорные контейнеры.

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

Отсутствие сегрегации интерфейса

Все это очень хорошо, но можно лучше.

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

Мы заменим зависимость от «швейцарского ножа» на одноцелевой инструмент, как в приведенном ниже фрагменте кода.

Обратите внимание: UpdateUserCommand и UserManager остались почти такими же, только с несколькими изменениями.

Вот как легко очистить грязный код:

Извлеченные интерфейсы из UserManager

Задумайтесь на секунду о последствиях в отношении тестирования и создания пользовательских реализаций IUserUpdater. Все становится намного легче.

Разделите свои классы

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

Итак, класс ниже нарушает несколько принципов. Он создаёт заново свою зависимость от AppDatabase. И так делать точно не надо.

Класс, нарушающий принцип DIP и явных зависимостей

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

Посмотрите на переделанный класс GetUsersCommand. Не создавайте новые классы и принимайте интерфейсы в качестве аргументов конструктора. Честно, это гораздо проще.

Выполнен рефакторинг класса для соблюдения DIP

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

«Количество классов растёт, как лавина, с каждым рефакторингом!»

Возможно, вы возразите против моего подхода к рефакторингу.

Помните, что рефакторинг не сводится к минимизации кода или даже к тому, чтобы сделать всё «проще». Речь идет об изменении кода в соответствии со сложностью приложения и облегчении работы с ним.

Упрощение != легкость.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи: Nicklas Millard, “Refactoring From Trash to SOLID”

Предыдущая статьяPython 3.9
Следующая статьяРазработка и развёртывание приложения машинного обучения: полное руководство