21 Ноя 2022
11 мин
50

Что такое рефакторинг кода

Рассказываем, что можно изменить с помощью рефакторинга кода.

Преимущества переработки кода

Содержание

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

Зачем нужен рефакторинг

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

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

Американский программист и автор множества книг по разработке ПО Мартин Фаулер определяет рефакторинг как «изменение внутренней структуры ПО без изменения его наблюдаемого поведения, призванное облегчить его понимание и удешевить модификацию». Разработчики переписывают какие-то части кода, чтобы они стали понятнее, лаконичнее, а еще — чтобы было проще добавлять новую функциональность.

Что не является рефакторингом

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

Дебаггинг

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

Улучшение функциональности ПО

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

Оптимизация

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

Рефакторинг и проектирование

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

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

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

Когда без рефакторинга не обойтись

Плановый рефакторинг

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

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

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

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

По мере необходимости *

* Когда добавляется новая функция и нужно исправить ошибку при разборе кода.

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

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

Проблемы, которые решит рефакторинг

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

Мертвый код

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

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

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

Повторяющийся код

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

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

Имя элемента кода не отражает его значение

Еще одна проблема, которая распространена у новичков: название элемента или объекта не отражает его значения. Например, переменная имеет очень общее название или однобуквенное обозначение. Это сильно усложняет читаемость кода, а следовательно, и его дальнейшую поддержку.

До рефакторинга

...
def new_item(self, a):
   if self.b.n + 1 <= self.b.mn and self.b.w + a.w < self.b.mw:
       self.b.new_item(a)
...

После рефакторинга

...
def add_item_to_backpack(self, item):
   has_place_ = self.backpack_.n_items_ + 1 <= self.backpack_.max_n_items_
   can_carry_ = self.backpack_.weight_ + item.weight < self.backpack_.max_weight_
   if has_place_ and can_carry_:
       self.backpack_.add_new_item(item)
...

Обратите внимание на пример. Думаю, что все согласятся: во втором случае код более понятный.

Чрезмерно длинные методы

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

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

Слишком длинные классы

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

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

Избыточное количество комментариев

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

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

Проектирование впрок

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

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

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

Методики рефакторинга

Red-Green-Refactor

Одной из популярных методик рефакторинга является Red-Green-Refactor. Написание программы происходит в три этапа:

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

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

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

Red-Green-Refactor

Визуализация методики Red-Green-Refactor

Абстракция

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

Рассмотрим на примере кода из компьютерной игры. В первом случае у разработчика есть два класса — Warrior и Archer, — у которых повторяются свойства name и heath_points. Тогда разработчик создаст новый класс Unit, в который унесет свойства name и heath_points, а специфичные методы has_shield и n_arrows оставит в изначальных классах.

До рефакторинга

class Warrior():
...
    @property
    def name(self):
        return self._my_name

    @property
    def heath_points(self):
        return self._hp

    @has_shield.setter
    def has_shield(self, shield):
	  if shield and not self._has_shield:
	     self._has_shield = True
	  ...
...

class Archer():
...
    @property
    def name(self):
        return self._my_name

    @property
    def heath_points(self):
        return self._hp

    @n_arrows.setter
    def n_arrows(self, number):
        if number < self._max_arrows:
	      self._n_arrows = number
...

После рефакторинга

class Unit():
...
    @property
    def name(self):
        return self._my_name

    @property
    def heath_points(self):
        return self._hp
... 

class Warrior(Unit):
...
    @has_shield.setter
    def has_shield(self, shield):
	  if shield and not self._has_shield:
		self._has_shield = True
	  ...
...

class Archer(Unit):
...
    @n_arrows.setter
    def n_arrows(self, number):
	  if number < self._max_arrows:
		self._n_arrows = number
...

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

Фрагментация методов

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

Есть много техник, чтобы достичь результата. Рассмотрим несколько из них на примере игры из предыдущего пункта:

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

До рефакторинга

class Unit():
...
    def go_forward_and_left(self, steps):
        self.coordinates.y += steps
        self.coordinates.x -= steps
...

После рефакторинга

class Unit():
...
    def go_forward(self, steps):
        self.coordinates.y += steps

    def go_left(self, steps):
        self.coordinates.x -= steps
...
  • Извлечение переменной помогает упростить выражение, которое сложно для восприятия. Например, условие имеет много сравнений и непонятно с первого взгляда.

До рефакторинга

class Archer(Unit):
...
    def attack(self, enemy):
        if (not self.has_bow or self.n_arrows < 1 or \\
            Unit.distance(enemy) > 5.0 or \\
            Unit.distance(enemy) < 1.0) and \\
            (not self.has_dagger or Unit.distance(enemy) > 1.0):
            # do not attack
        ...
...

После рефакторинга

class Archer(Unit):
...
    def attack(self, enemy):
        can_shoot = self.has_bow and self.n_arrows > 0
        in_shoot_range = Unit.distance(enemy) <= 5.0 and \\
                         Unit.distance(enemy) > 1.0
        can_use_dagger = self.has_dagger and \\
				  Unit.distance(enemy) <= 1.0

        if not (can_shoot and in_shoot_range) and not can_use_dagger:
           # do not attack
        ...
...

Риски рефакторинга

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

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

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

Коротко о главном

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

Выше мы рассмотрели некоторые практики, которые полезны и эффективны в работе любого разработчика. Чтобы глубже погрузиться в тему рефакторинга, можно изучить книги Мартина Фаулера «Рефакторинг. Улучшение существующего кода» и «Совершенный код» Стива Макконнелла (глава 24 — «Рефакторинг»).


Получите пошаговый план развития в карьере после записи на курс






    Оставляя заявку, вы принимаете условия соглашения

    Учёба ради учёбы — это не прикольно. На наших курсах вы ставите конкретные цели и достигаете
    их
    в заданные сроки. Начинайте карьеру с первых достижений!

    Добавить комментарий

    Вставить формулу как
    Блок
    Строка
    Дополнительные настройки
    Цвет формулы
    Цвет текста
    #333333
    Используйте LaTeX для набора формулы
    Предпросмотр
    \({}\)
    Формула не набрана
    Вставить