4.5.1. Рефакторинг
Основным способом борьбы с деградацией является рефакторинг. или реорганизация кода – процесс изменения внутренней структуры программы, не затрагивающий ее внешнего поведения и имеющий целью облегчить понимание её работы. В основе рефакторинга лежит последовательность небольших эквивалентных (то есть сохраняющих поведение) преобразований. Поскольку каждое преобразование маленькое, программисту легче проследить за его правильностью, и в то же время вся последовательность может привести к существенной перестройке программы и улучшению её согласованности и четкости. Основные идеи постоянного рефакторинга:
- «код с душком»;
- дублирование кода;
- наследование;
- выделение методов.
Рефакторинг нужно применять постоянно при разработке кода. Основными стимулами его проведения являются следующие задачи:
- Необходимо добавить новую функцию, которая недостаточно укладывается в принятое архитектурное решение;
- Необходимо исправить ошибку, причины возникновения которой сразу не ясны;
- Преодоление трудностей в командной разработке, которые обусловлены сложной логикой программы.
Во многом при рефакторинге лучше полагаться на интуицию, основанную на опыте. Тем не менее имеются некоторые видимые проблемы в коде, требующие рефакторинга:
- Длинный метод или большой класс:
- выделение классов;
- выделение подклассов.
- Длинный список параметров:
- получение необходимых значений у параметров-объектов;
- агрегирование группы параметров в объект.
- Расходящиеся модификации (один класс модифицируется по разным поводам):
- Длинный список параметров:
- выделение классов, отвечающих за разные вещи.
- «Стрельба дробью»: чтобы сделать что-то, нужно внести много маленьких изменений в разных местах:
- перемещение членов;
- встраивание.
- «Завистливые функции»: метод пользуется другим классом больше, чем своим:
- перемещение членов;
- Группы данных:
- агрегация параметров в объект;
- получение данных у одного объекта.
- Одержимость элементарными типами:
- замена примитивов с внешними операциями на объекты.
- Оператор switch:
- замена логики полиморфизмом.
- Параллельные иерархии наследования.
- «Ленивый класс»:
- удаление класса.
- Теоретическая общность:
- удаление сложных общих механизмов.
- Временное поле:
- выделение временных полей в отдельный класс.
- Цепочки сообщений, закон Деметры:
- перемещение метода.
- Посредники:
- встраивание методов;
- удаление класса.
- Неуместная близость:
- перемещение членов;
- выделение класса.
- Альтернативные классы с разными интерфейсами:
- переименование метода;
- перемещение метода;
- удаление класса.
- Неполнота библиотечного класса:
- локальное расширение.
- Классы данных:
- при излишнем доступе снаружи — удаление устанавливающего метода;
- перемещение методов в классы данных.
- Отказ от наследования: лишняя функциональность в базовом классе.
- Комментарии:
- переименование;
- поясняющий метод или переменная;
- встраивание переменной;
- введение утверждения;
- разработка через рефракторинг.
Главная идея – сделать попроще, чтобы потом можно было переделать. И это не пустая трата времени — так зачастую быстрее.
Основными недостатками рефакторинга являются:
- Главное: поведение меняться не должно.
- Проблемы:
- много зачастую очень мелких изменений;
- мелкие изменения могут приводить к трудно обнаруживаемым ошибкам;
- изменения могут затрагивать разные части системы.
При своих недостатках рефракторинг гарантирует корректность в следующем:
- Утверждения:
- их часто приходится изменять;
- до них трудно добраться при выполнении.
- Автоматические тесты:
- приемочные;
- блочные или модульные.
В целом, при разработке программных систем и оценке качества термин рефакторинг означает изменение исходного кода программы без изменения его внешнего поведения. Во многих методологиях рефакторинг является неотъемлемой частью жизненного цикла разработки ПО: разработчики попеременно то создают новые тесты и функциональность, то выполняют рефакторинг кода для улучшения его логичности и прозрачности. При рефакторинге автоматическое юнит-тестирование проверит существующую функциональность и отсутствие или наличие изменений в ней.
Однако рефакторинг изначально не предназначен для исправления ошибок и добавления новой функциональности. Он вообще не меняет поведение программного обеспечения и это помогает избежать ошибок и облегчить добавление функциональности. Он применяется для улучшения понятности кода или изменения его структуры, для удаления «мёртвого кода» — всё это для того, чтобы в будущем код было легче поддерживать и развивать.
В частности, добавление в программу нового поведения может оказаться сложным с существующей структурой — в этом случае разработчик может выполнить необходимый рефакторинг, а уже затем добавить новую функциональность.
Это может быть перемещение поля из одного класса в другой, вынесение фрагмента кода из метода и превращение его в самостоятельный метод или даже перемещение кода по иерархии классов. Каждый отдельный шаг может показаться элементарным, но совокупный эффект таких малых изменений в состоянии радикально улучшить проект или даже предотвратить распад плохо спроектированной программы.
Наиболее употребимые [4] методы рефакторинга:
- Изменение сигнатуры (Change Method Signature)
- Инкапсуляция поля (Encapsulate Field)
- Выделение класса (Extract Class)
- Выделение интерфейса (Extract Interface)
- Выделение локальной переменной (Extract Local Variable)
- Выделение метода (Extract Method)
- Генерализация типа (Generalize Type)
- Встраивание (Inline)
- Введение фабрики (Introduce Factory)
- Введение параметра (Introduce Parameter)
- Подъём метода (Pull Up Method)
- Спуск метода (Push Down Method)
- Переименование метода (Rename Method)
- Перемещение метода (Move Method)
- Замена условного оператора полиморфизмом (Replace Conditional with Polymorphism)
- Замена наследования делегированием (Replace Inheritance with Delegation)
- Замена кода типа подклассами (Replace Type Code with Subclasses)