4.5.1. Рефакторинг

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

  • «код с душком»;
  • дублирование кода;
  • наследование;
  • выделение методов.

Рефакторинг нужно применять постоянно при разработке кода. Основными стимулами его проведения являются следующие задачи:

  1. Необходимо добавить новую функцию, которая недостаточно укладывается в принятое архитектурное решение;
  2. Необходимо исправить ошибку, причины возникновения которой сразу не ясны;
  3. Преодоление трудностей в командной разработке, которые обусловлены сложной логикой программы.

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

  1. Длинный метод или большой класс:
    • выделение классов;
    • выделение подклассов.
  2. Длинный список параметров:
    • получение необходимых значений у параметров-объектов;
    • агрегирование группы параметров в объект.
  3. Расходящиеся модификации (один класс модифицируется по разным поводам):
  4. Длинный список параметров:
    • выделение классов, отвечающих за разные вещи.
  5. «Стрельба дробью»: чтобы сделать что-то, нужно внести много маленьких изменений в разных местах:
    • перемещение членов;
    • встраивание.
  6. «Завистливые функции»: метод пользуется другим классом больше, чем своим:
    • перемещение членов;
  7. Группы данных:
    • агрегация параметров в объект;
    • получение данных у одного объекта.
  8. Одержимость элементарными типами:
    • замена примитивов с внешними операциями на объекты.
  9. Оператор switch:
    • замена логики полиморфизмом.
  10. Параллельные иерархии наследования.
  11. «Ленивый класс»:
    • удаление класса.
  12. Теоретическая общность:
    • удаление сложных общих механизмов.
  13. Временное поле:
    • выделение временных полей в отдельный класс.
  14. Цепочки сообщений, закон Деметры:
    • перемещение метода.
  15. Посредники:
    • встраивание методов;
    • удаление класса.
  16. Неуместная близость:
    • перемещение членов;
    • выделение класса.
  17. Альтернативные классы с разными интерфейсами:
    • переименование метода;
    • перемещение метода;
    • удаление класса.
  18. Неполнота библиотечного класса:
    • локальное расширение.
  19. Классы данных:
    • при излишнем доступе снаружи — удаление устанавливающего метода;
    • перемещение методов в классы данных.
  20. Отказ от наследования: лишняя функциональность в базовом классе.
  21. Комментарии:
    • переименование;
    • поясняющий метод или переменная;
    • встраивание переменной;
    • введение утверждения;
    • разработка через рефракторинг.

Главная идея – сделать попроще, чтобы потом можно было переделать. И это не пустая трата времени — так зачастую быстрее.

Основными недостатками рефакторинга являются:

  1. Главное: поведение меняться не должно.
  2. Проблемы:
    • много зачастую очень мелких изменений;
    • мелкие изменения могут приводить к трудно обнаруживаемым ошибкам;
    • изменения могут затрагивать разные части системы.

При своих недостатках рефракторинг гарантирует корректность в следующем:

  1. Утверждения:
    • их часто приходится изменять;
    • до них трудно добраться при выполнении.
  2. Автоматические тесты:
    • приемочные;
    • блочные или модульные.

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

Однако рефакторинг изначально не предназначен для исправления ошибок и добавления новой функциональности. Он вообще не меняет поведение программного обеспечения и это помогает избежать ошибок и облегчить добавление функциональности. Он применяется для улучшения понятности кода или изменения его структуры, для удаления «мёртвого кода» — всё это для того, чтобы в будущем код было легче поддерживать и развивать.

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

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

Наиболее употребимые [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)