Фаулер Мартин. Рефакторинг. Улучшение проекта существующего кода 2019

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

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

Каталог:

  • «Выделение класса» (Extract Class )
  • «Выделение метода» (Extract Method )
  • «Перемещение метода» (Move Method )
  • «Подъем поля» (Pull Up Field )
  • «Замена условного оператора полиморфизмом» (Replace Conditional with Polymorphism)
  • «Замены кода типа состоянием/стратегией» (Replace Type Code with State / Strategy ).
  • «Самоинкапсуляция поля» (Self Encapsulate Field )
  • «Формирования шаблона метода» (Form Template Method )
  • «Замены временной переменной вызовом метода» (Replace Temp with Query)
  • «Замещение алгоритма» (Substitute Algorithm )
  • «Введение граничного объекта» (Introduce Parametr Object )
  • «Декомпозиция условных операторов» (Decompose Conditional )
  • «Замена значения данных объектом» (Replace Data Value with Object )
  • «Сокрытие делегирования» (Hide Delegate )

«Выделение метода» (Extract Method ) Когда в методе есть код который должен быть выделен (по разным причинам) в новый отдельный метод

«Перемещение метода» (Move Method ) Когда метод должен быть (если метод оперирует не своими данными класса, а другого) перемещен в другой класс

«Замена условного оператора полиморфизмом» (Replace Conditional with Polymorphism) Когда будут добавлятся новые типы

«Замены временной переменной вызовом метода» (Replace Temp with Query) Что бы избавится от временных переменных

«Введение граничного объекта» (Introduce Parametr Object ) Можно сократить длинные списки параметров

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

Код с душком

Дублирование кода Увидев одинаковые кодовые структуры в нескольких местах, можно быть уверенным, что если удастся их объединить, программа от этого только выиграет.

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

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

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

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

«Стрельба дробью» Когда при выполнении любых модификаций приходится вносить множество мелких изменений в большое число классов.

Завистливые функции Весь смысл объектов в том, что они позволяют хранить данные вместе с процедурами их обработки. Классический пример дурного запаха - метод, который больше интересуется не тем классом, в котором он находится, а каким-то другим. Чаще всего предметом зависти являются данные. Не счесть случаев, когда мы сталкивались с методом, вызывающим полдюжины методов доступа к данным другого объекта, чтобы вычислить некоторое значение. Разумеется, есть несколько сложных схем, нарушающих это правило. На ум сразу приходят паттерны «Стратегия» (Strategy pattern, Gang of Four) и «Посетитель» (Visitor pattern, Gang of Four)

Группы данных Элементы данных - как дети: они любят собираться в группы, повторяющиеся аргументы идущие вместе.

Одержимость элементарными типами

Операторы типа switch Одним из очевидных признаков объектно-ориентированного кода служит сравнительная немногочисленность операторов типа switch (или case). Проблема, обусловленная применением switch, по существу, связана с дублированием. Часто один и тот же блок switch оказывается разбросанным по разным местам программы. При добавлении в переключатель нового варианта приходится искать все эти блоки switch и модифицир

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

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

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

Отказ от наследства Подклассам полагается наследовать методы и данные своих родителей. Но как быть, если наследство им не нравится или попросту не требуется? Получив все эти дары, они пользуются лишь малой их частью. Обычная история при этом - неправильно задуманная иерархия. Необходимо создать новый класс на одном уровне с потомком и с помощью «Спуска метода» (Push Down Method ) и «Спуска поля» (Push Down Field ) вытолкнуть в него все бездействующие методы. Благодаря этому в родительском классе будет содержаться только то, что используется совместно.

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

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

В книге хороший разбор примеров, которые разбирают приемы рефакторинга из каталога приемов рефакторинга.

Refactoring.guru

Технический долг

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

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

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

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

Отсутствие авто-тестов Отсутствие немедленной обратной связи поощряет быстрые, но рискованные исправления и «костыли», иногда прямо на продакшене. Эффекты от этого бывают катастрофические. Например, невинный хот-фикс рассылает тестовое письмо по всей базе клиентов или удаляет реальные данные клиентов в базе данных.

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

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

Долговременная одновременная разработка в нескольких ветках Может вызвать накопление технического долга, который необходимо восполнить при слиянии изменений воедино. Чем больше изменений, которые сделаны изолировано, тем больше итоговый технический долг.

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

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

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

Отсутствие компетенции Когда разработчик просто не умеет писать качественный код.

Код с душком

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

  • Длинный метод

    • Метод содержит слишком большое число строк кода. Длина метода более десяти строк должна начинать вас беспокоить.Лечение:Вынести код в отдельный метод.
  • Большой класс

    • Класс содержит множество полей/методов/строк кода.Лечение:Вынести код в отдельный класс.
  • Одержимость элементарными типами

    • Использование элементарных типов вместо маленьких объектов для небольших задач (например, валюта, диапазоны, специальные строки для телефонных номеров и т. п.)
    • Использование констант для кодирования какой-то информации (например, константа USER_ADMIN_ROLE = 1 для обозначения пользователей с ролью администратора).
    • Использование строковых констант в качестве названий полей в массивах. Лечение:Замена переменных обьектом.
  • Длинный список параметров

    • Количество параметров метода больше трёх-четырёх. Лечение:Замена параметров передачей обьекта.
  • Группы данных

    • Иногда в разных частях кода встречаются одинаковые группы переменных (например, параметры подключения к базе данных). Такие группы следует превращать в самостоятельные классы.

Нарушители объектного дизайна Все эти запахи являют собой неполное или неправильное использование возможностей объектно-ориентированного программирования.

  • Альтернативные классы с разными интерфейсами

    • Два класса выполняют одинаковые функции, но имеют разные названия методов.
  • Отказ от наследства

    • Если подкласс использует лишь малую часть унаследованных методов и свойств суперкласа, это является признаком неправильной иерархии.При этом ненужные методы могут просто не использоваться либо быть переопределёнными и выбрасывать исключения.Лечение: убрать наследование и добавить делегирование, добавив поле с типом базового класса мы можем реализовать вызываемые ранее методы внутри подкласса используя обьект суперкласса.Или если наследование имеет смысл то разбить суперкласс на используемые части и уже от нужной части наследоваться.
  • Операторы switch

    • У вас есть сложный оператор switch или последовательность if-ов. Лечение: заменой полиморфизмом или шаблоном состоянием/стратегией или перенос вариантов в отдельные подклассы. Из-за того, что некоторые методы возвращают null вместо реальных объектов, у вас в коде присутствует множество проверок на null . Вместо null возвращайте Null-объект, который предоставляет поведение по умолчанию.
  • Временное поле

    • Временные поля – это поля, которые нужны объекту только при определённых обстоятельствах. Только тогда они заполняются какими-то значениями, оставаясь пустыми в остальное время.Лечение:Зачастую временные поля создаются для использования в алгоритме, который требует большого числа входных данных. Так, вместо создания большого числа параметров в таком методе, программист решает создать для этих данных поля в классе. Эти поля используются только в данном алгоритме, а в остальное время простаивают.Временные поля и весь код, работающий с ними, можно поместить в свой собственный класс с помощью извлечения класса. По сути, вы создадите объект-метод.

Введите Null-объект и встройте его вместо кода проверок на наличие значений во временных полях.

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

  • Расходящиеся модификации

    • При внесении изменений в класс приходится изменять большое число различных методов. Например, для добавления нового вида товара вам нужно изменить методы поиска, отображения и заказа товара в этом же классе.Лечение:Разделите поведения класса, используя извлечение класса.
  • Параллельные иерархии наследования

    • Всякий раз при создании подкласса какого-то класса приходится создавать ещё один подкласс для другого класса.Лечение:Вы можете попытаться устранить дублирования паралельных классов в два этапа. Во-первых, нужно заставить экземпляры одной иерархии ссылаться на экземпляры другой иерархии. Затем следует убрать иерархию в ссылающемся классе с помощью перемещения метода и перемещения поля.
  • Стрельба дробью

    • При выполнении любых модификаций приходится вносить множество мелких изменений в большое число классов.Лечение:Собрать все изменения в одном классе.

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

  • Комментарии

    • Метод содержит множество поясняющих комментариев.Лечение:Если комментарий обьясняет сложное выражение то создайте временную переменную с внятным именем обьясняющим эту логику выражения.Если комментарий обьясняет целый блок кода то вынесите этот блок в отдельный метод.Если поясняется определенные условия работы то внедрите конкретные проверки, assert.
  • Дублирование кода

    • Два фрагмента кода выглядят почти одинаковыми.Лечение: извлечением метода или через вынесения общей логики в суперкласс и переопределения отличающейся логики в конкретном месте.
  • Класс данных

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

    • Переменная, параметр, поле, метод или класс больше не используются (чаще всего потому, что устарели).
  • Ленивый класс

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

    • Класс, метод, поле или параметр не используются.

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

  • Завистливые функции

    • Метод обращается к данным другого объекта чаще, чем к собственным данным.Лечение:Следует придерживаться такого правила: то, что изменяется одновременно, нужно хранить в одном месте. Обычно данные и функции, использующие эти данные, также изменяются вместе (хотя бывают исключения).
  • Неуместная близость

    • Один класс использует служебные поля и методы другого класса.Лечение: Перенос методов и полей в используймый класс или выделение в отдельный класс или заменить двунапрвленную связь на однонапрвленную или если близость между суперклассом и наследником замените делегирование на наследование.
  • Неполнота библиотечного класса

    • Автор библиотеки не предусмотрел возможности, которые вам нужны, либо отказался их внедрять.Лечение:Создайте новый класс, который бы содержал эти методы, и сделайте его наследником служебного класса, либо его обёрткой.Или если только метода не хватает то добавьте метод в клиентский класс и передавайте в него объект служебного класса в качестве аргумента.
  • Цепочка вызовов

    • Вы видите в коде цепочки вызовов вроде такой $a->b()->c()->d(). Такие последовательности вызовов означают, что клиент связан с навигацией по структуре классов. Любые изменения промежуточных связей означают необходимость модификации клиента.Чем меньше клиентский код знает подробностей о связях между объектами, тем проще будет впоследствии вносить изменения в программу.Лечение:Создайте новый метод в классе А, который бы делегировал вызов объекту B. Таким образом, клиент перестанет знать о классе В и зависеть от него.Для каждого метода класса-делегата, вызываемого клиентом, нужно создать метод в классе-сервере, который бы делегировал вызов классу-делегату.
  • Посредник

    • Если класс выполняет одно действие – делегирует работу другому классу – стоит задуматься, зачем он вообще существует.Данный запах может быть результатом фанатичной борьбы с цепочками вызовов.Лечение: Удалить посредника но не удаляеть если он был специально создан для избавления от нежелательной зависимости между классами или если используются паттерны Заместитель или Декоратор

Приёмы рефакторинга

...