Привет, Хабр!

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

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

Наливайте себе чай, берите плед и устраивайтесь поудобнее, дальше у нас большой разбор!

1. Что такое рефакторинг?

Мартин Фаулер, автор книги «Рефакторинг. Улучшение существующего кода», приводит следующее определение для данного процесса (перевод С. Маккавеева):

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

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

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

2. Для чего проводят рефакторинг?

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

Снижение технического долга

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

Улучшение читаемости и тестируемости кода

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

Повышение эффективности разработчиков

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

3. Из-за чего возникает необходимость в рефакторинге

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

Недостаточная тестируемость

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

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

Изменение требований

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

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

Отсутствие договоренностей

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

1. Отсутствие общего стиля написания кода

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

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

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

2. Отсутствие встреч по обмену знаниями

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

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

3. Отсутствие или игнорирование единых инструментов

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

К примеру, различные инструменты, такие как IDE (интегрированная среда разработки), линтеры или даже инструменты для форматирования кода, могут иметь разные настройки и предпочтения в отношении стиля (например, разную ширину кода – 80 или 120 символов). Из-за такого разнообразия инструментов и настроек может потребоваться куда больше времени и усилий для обучения новичков, ведь если в команде нет общепринятых инструментов и правил, то начинающего разработчика куда сложнее погрузить в процесс.

4. Откуда берется страх перед рефакторингом

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

Проблема понимания

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

1. Нарушение функциональности

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

2. Нарушение архитектурных решений проекта

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

3. Усложнение сопровождения

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

Проблема времени

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

Проблема неопределенности результатов

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

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

5. Как отбросить страх и сомнения

Выбирайте подходящий момент

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

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

  1. Перед началом нового спринта. Изменения вместе с новой функциональностью тщательно проверяются в конце спринта в период проведения регрессионного тестирования.

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

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

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

Проводите код-ревью

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

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

Используйте вспомогательные инструменты

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

Автоматизируйте процессы

Автоматизация в CI/CD (Continuous Integration/Continuous Deployment) используется для обеспечения требований к коду в процессе разработки и доставки программного обеспечения.

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

  • GitHub Actions

  • Gitlab CI/CD

  • Jenkins

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

Рефакторинг через TDD

TDD (Test-Driven Development) — методология разработки программного обеспечения, при которой тесты пишутся перед написанием кода. Чаще всего разработчики не находят ему регулярного применения, но его можно смело использовать при проведении рефакторинга.

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

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

  2. Код проходит рефакторинг без изменения самой функциональности.

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

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

При налаженном подходе написания автотестов, данный процесс занимает не так много времени, поэтому поначалу стоит сосредоточиться на набивании руки для использования этого подхода. Начать свое погружение в данную тему можно с книги К. Бека «Экстремальное программирование. Разработка через тестирование».

Безопасный рефакторинг

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

  1. Изменение структуры проекта. В результате изменения структуры при рефакторинге кода, проект становится организованным, понятным и легко поддерживаемым.

  2. Улучшение именования. Хорошее именование является важным аспектом читаемости кода. Стоит обратить внимание на имена переменных, методов и классов, чтобы они были понятными и отражали свою функциональность.

  3. Удаление «мертвого» кода. Неиспользуемый код сам по себе не влияет на работоспособность программного обеспечения, но он отнимает время у разработчиков, которые сталкиваются с ним в самых дальних уголках проекта.

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

Резюмируя

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

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

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

  • Не забывайте делиться опытом, проводить код-ревью, выбирать единые инструменты и принимать соглашения в команде.

  • Вкладывайтесь в процесс. Внедряйте и автоматизируйте процессы по контролю качества кода на любом из этапов разработки.

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

Заключение

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

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

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

Комментарии (5)


  1. JordanCpp
    19.12.2023 10:59

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


    1. Holofox Автор
      19.12.2023 10:59

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


  1. blainepwnz
    19.12.2023 10:59

    на словах оно всегда красиво, а на деле никакое tdd не спасет от рандомных или совсем неочевидных регрессий.

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


    1. Holofox Автор
      19.12.2023 10:59

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

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


      1. blainepwnz
        19.12.2023 10:59

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

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

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