Тесты Пообщаться с GPT Протестировать код
Программирование Аналитика Дизайн Маркетинг Управление проектами
28 Фев 2023
12 мин
4116

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

Пройдите тест, узнайте какой профессии подходите

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

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

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

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

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

Рефакторинг приводит код программы в порядок. Чтобы точнее понять, что такое рефакторинг кода, можно обратиться к определению американского программиста Мартина Фаулера. Он говорит, что рефакторинг — это когда внутреннюю структуру программного обеспечения меняют так, чтобы она стала проще, а программа при этом вела себя как обычно.

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

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

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

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

Вопрос «что такое рефакторинг» не всегда имеет однозначный ответ. Иногда так называют процессы, которые меняют структуру кода, но на самом деле к рефакторингу не относятся.

Рассмотрим примеры процессов, которые только похожи на рефакторинг.

Дебаггинг

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

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

Поэтому смешивать рефакторинг и дебаггинг нельзя.

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

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

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

Оптимизация

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

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

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

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

Можно сократить время, которое тратится на улучшение кода.

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

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

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

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

Разберем, в каких ситуациях рефакторинг пропустить нельзя.

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

Современные методологии разработки — это частые правки уже написанного кода, из-за чего может быть сложно разобраться, как программа устроена.

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

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

На курсе «Java-разработчик» в Skypro вы научитесь писать чистый код, отлаживать программы и находить ошибки в чужом коде. Курс рассчитан на начинающих: стартуем с самых азов и постепенно выходим на уровень уверенного новичка.

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

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

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

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

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

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

Мертвый код

Мертвый код — это фрагменты, которые программа нигде не использует. Лучше их удалить и проверить, нет ли еще где-то похожих участков.

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

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

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

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

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

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

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

Еще одна распространенная проблема новичков — некорректное название переменной или объекта. Например, переменная с общим именем вроде data или однобуквенное название. Такой код сложнее читать, а значит, поддерживать.

Посмотрите пример кода, который проверяет, можно ли добавить новый предмет в «рюкзак» (backpack). Предмет добавляется, но только если рюкзак еще не заполнен и суммарный вес не превышает максимум.

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 — одна из самых известных методик. Работа над кодом идет в три шага:

  • Red. Сначала пишутся тесты, которые дадут проверить, решает ли код задачу.
  • Green. Разработчики пишут минимальный код, который выполняет нужные функции и проходит тесты.
  • Refactor. Этап рефакторинга. Здесь разработчики упорядочивают структуру, удаляют ненужные части и делают код чище.

Фактически разработка делится на два этапа. Сначала вы концентрируетесь только на том, чтобы закрыть задачу (Green). Затем — на самом рефакторинге, чтобы улучшить код (Refactor). Это помогает не смешивать две задачи.

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

круговая схема

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

Абстракция

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

Пример: есть приложение-игра. В игре есть классы Warrior и Archer, в которых повторяются свойства name и heath_points.

Вместо того чтобы повторять свойства в разных классах, можно создать класс Unit с общими свойствами name и heath_points. А классы Warrior и Archer оставить только для специфичных вещей: 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

Можно вынести методы и поля из общего класса, если они нужны только один раз. Например, если в Unit хранится свойство has_shield, но на практике оно нужно только Warrior, стоит перенести его в Warrior. То же самое с полями и методами, необходимыми только Archer: если в Unit остался код, связанный с количеством стрел (n_arrows) и он не используется нигде больше, лучше перенести его в Archer.

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

Эта методика помогает упростить код внутри метода и выделить его основную идею. У этой методики есть много техник. Рассмотрим несколько из них на примере игры из предыдущего пункта:

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

Разница до и после рефакторинга

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

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

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

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

Рефакторинг — сложная задача. Вы вносите изменения в код, который уже работает и выполняет определенные задачи. Если плохо разобраться в коде, что-то пропустить, можно «поломать» приложение.

Обязательно сохраняйте исходный код перед правками или используйте систему контроля версий. Делайте контрольные точки: если что-то пойдет не так, вы всегда сможете откатиться к предыдущему состоянию. И, конечно, не забудьте о тестах — они помогают вовремя обнаружить проблемы.

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

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

  • Рефакторинг делает код проще и понятнее, но не меняет его функциональность.
  • Рефакторинг — это не отладка. Во время отладки исправляют ошибки и сбои, а при рефакторинге делают уже рабочий код понятнее и аккуратнее.
  • Если добавляете новые функции, заставляете программу работать быстрее или расходовать меньше ресурсов — вы меняете код. Новый код тоже может оказаться запутанным и сложным.
  • Поэтому все эти процессы — не рефакторинг.
  • Хорошо продуманная архитектура может сократить затраты на рефакторинг. Если программисты заранее обдумывают логику и структуру приложения, код требует меньше изменений.
  • Регулярные правки быстро запутывают код. Если проводить рефакторинг регулярно, проект будет легче поддерживать и развивать.
  • Во время рефакторинга разработчик убирает мертвый код, дубли, заменяет имена переменных и классов на более понятные.
  • Есть много методик рефакторинга. Самая простая и понятная — методика Red-Green-Refactor. Сначала пишете тесты, потом создаете минимальный код, который сможет пройти тесты, и только потом улучшаете и переписываете.
  • Если решили провести рефакторинг, используйте систему контроля версий и сделайте контрольную точку. Во время рефакторинга можно что-то поломать.

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