Здравствуйте, в этой статье мы постараемся ответить на вопрос: «Что такое рефакторинг кода и когда он нуже». Если у Вас нет времени на чтение или статья не полностью решает Вашу проблему, можете получить онлайн консультацию квалифицированного юриста в форме ниже.
Есть гласное правило для всех программистов – код должен быть лаконичным, хорошо структурированным и понятным для разработчиков, работающих с ним. Проблема в том, что написать такой код с первого раза – очень сложная задача. Каким бы опытным ни был программист, начальство заставит его спешить, заказчики будут менять требования по ходу разработки, а иногда код будет становиться непонятным из-за банального недосыпа. Более того, сами языки программирования регулярно совершенствуются и обретают новые возможности, позволяя заметно сократить количество кода. Поэтому и нужен рефакторинг.
Есть еще несколько методов, использование которых поможет провести грамотный refactoring программы и сделать ее структуру более понятной:
- Непонятные имена. Все названия классов, методов, переменных и других конструкций кода должны иметь интуитивно-понятные наименования, по которым можно понять, что именно они делают. Следование правилу сделает структуру понятной для других разработчиков.
- Большие методы и классы. Громоздкие конструкции сокращаются путем вынесения фрагментов в компактные методы и классы, которые правильно ссылаются друг на друга.
- Длинные списки параметров. Здесь работает аналогичное правило, что и выше. Число аргументов сокращается максимум до четырех-пяти, иначе структура программы будет сложной.
- Цепочки сообщений. Метод любого объекта должен ссылаться только на собственные либо переданные извне параметры, а также на те участки кода, к которому есть прямой доступ. Это правило называется законом Деметры. Если в программе это не так, нужно перепроверить все связи и правильно их настроить.
- Статика. Обилие статических переменных увеличивает непредсказуемость программы и делает код более процедурным, нежели объектно-ориентированным. Чтобы избежать этого, разработчик должен инстанцировать объекты, позволяя им управлять данными так, как им нужно.
- Универсальные объекты. Такие конструкции, имеющие доступ к другим универсальным участкам программы, нарушают закон Деметры и увеличивают связность системы. Это не идет на пользу приложению, поэтому в процессе рефакторинга важно инжектировать только необходимые зависимости.
Увеличить эффективность и скорость рефакторинга помогут хорошие тесты – функциональные, интеграционные и unit-тесты. Необходимо перепроектировать программу небольшими итерациями, после каждого такого шага проводить тестирование, и, если все в порядке, продолжать процедуру.
Выше приведены основные идеи того, как провести refactoring кода и устранить большинство типичных ошибок. Грамотный разработчик должен делать рефакторинг регулярно, а не от случая к случаю. Только так можно упростить задачу, сократить время и силы, необходимые на реструктуризацию программы.
Рефакторинг и проектирование
Рефакторинг — дорогой для бизнеса процесс. Программисты получают зарплату, но не добавляют новых функций и не ускоряют код. Вместо этого они переписывают уже существующий. Данный процесс зачастую необходим и может сохранить много времени и денег в будущем, но возможно ли убрать полностью или сократить время, которое тратится на рефакторинг? Ответ: да, можно.
Еще до написания нужно подумать об архитектуре будущего кода. Часто перед тем, как начать писать программу, программисты проектируют ее структуру. Это сильно помогает в создании качественного кода и позволяет тратить меньше времени на его рефакторинг в дальнейшем.
Кроме того, проще думать над структурой программы, когда она находится в разработке, чем в тот момент, когда она уже работает и участвует в бизнес-процессах.
- Обнаружив, что в программу необходимо добавить новую функциональность, но код программы не структурирован удобным для добавления этой функциональности образом, сначала произведите рефакторинг программы, чтобы упростить внесение необходимых изменений, а только потом добавьте функцию.
- Перед началом рефакторинга убедитесь, что располагаете надежным комплектом тестов. Эти тесты должны быть самопроверяющимися.
- При применении рефакторинга программа модифицируется небольшими шагами. Ошибку нетрудно обнаружить.
- Написать код, понятный компьютеру, может каждый, но только хорошие программисты пишут код, понятный людям.
Самый важный урок, который должен преподать данный пример, это ритм рефакторинга: тестирование, малые изменения, тестирование, малые изменения, тестирование, малые изменения. Именно такой ритм делает рефакторинг быстрым и надежным.
При проведении рефакторинга важным предварительным условием является наличие надежных тестов.
Правила разработки тестов
- Делайте все тесты полностью автоматическими, так чтобы они проверяли собственные результаты.
- Комплект тестов служит мощным детектором ошибок, резко сокращающим время их поиска.
- Чаще запускайте тесты. Запускайте тесты при каждой компиляции — каждый тест хотя бы раз в день.
- Получив сообщение об ошибке, начните с создания теста модуля, показывающего эту ошибку.
- Лучше написать и выполнить неполные тесты, чем не выполнить полные тесты.
- Подумайте о граничных условиях, которые могут быть неправильно обработаны, и сосредоточьте на них свои тесты.
- Не забывайте проверять, чтобы в случае возникновения проблем генерировались исключительные ситуации.
- Опасение по поводу того, что тестирование не выявит все ошибки, не должно помешать написанию тестов, которые выявят большинство ошибок.
Проблемы рефакторинга
Изначально понятие рефакторинга (refactoring) сформировалось применительно к Smalltalk, а потом уже концепция постепенно распространилась среди сторонников других языков программирования. Собственно, рефакторинг — это уже неотъемлемый элемент процесса разработки структуры приложений (framework development). Речь идет именно о рефакторинге, когда структурщики работают над иерархией классов и сокращением кодов.
Грамотные специалисты понимают, что слёту хорошую структуру не создать, она совершенствуется в ходе работы, как говорится «на опыте». И еще они знают, что чаще речь идет об улучшении читаемости и модификации старого кода (а не о создании нового). Вот тут и вступает в силу рефакторинг, применимый и для всего ПО, и отдельно для структур (frameworks).
Связаны ли с рефакторингом какие-либо риски? Да, они есть. Дело в том, что анализу и рефакторингу подвергается чистый, уже работающий код, и по ходу дела в нем могут появиться ошибки, на поиск которых потом придется потратить целые дни, а то и недели. Еще хуже, когда рефакторинг проводится бессистемно и без соблюдения формальностей.
Оценка эффективности рефакторинга
Анализ и рефакторинг сделан успешно, если в результате вы имеете чистый, простой и понятный код.
К примеру, если количество покупателей в программе обозначено буквой Z, то лучше вместо неё поставить customerCount. Так код будет выглядеть яснее, и выполняемые в нём операции – тоже.
Если какая-то часть кода повторяется два раза и более, то есть смысл задать её в виде функции или метода. Тогда эти повторяющиеся фрагменты не нужно будет выискивать по всему коду, понадобится сделать замену лишь в одном месте.
Размеры функций, методов и классов тоже имеют значения. Если функция не влезает целиком в экран, то её нужно разбить на две части, тогда код будет легче читаться.
Еще один способ упростить код – все функции собрать в самостоятельный файл, а потом уже ввести его в программу.
Как встроить рефакторинг в свой рабочий процесс?
Обычно самый первый вопрос — как правильно выделить время под рефакторинг. Заводить ли для этого отдельные таски и когда рефакторить, если фичи в приоритете?
Ответ такой: рефакторинг должен проводиться непрерывно на всем этапе разработки.
Обычно это выглядит так:
— Вы взяли задачу и начали читать код. На этом моменте обычно вам уже придется что-то подрефакторить.
— Потом вы приступили к разработке. Буквально через 5-10 минут, максимум полчаса-час, у вас должен быть рабочий кусок кода, покрытый тестом. Тут же надо его рефакторить.
— Снова разработка, снова покрытый тестами кусок кода через непродолжительный отрезок времени.
И такими вот циклами и движетесь вперед до решения задачи. Как только вы наткнулись на кусок кода, в который ваша фича не лезет никак — останавливаетесь в прогрессе и начинаете рефакторить.
Если вы делаете это в парадигме TDD, то этот подход будет называться Red-Green-Refactor. То есть сначала пишете красный тест на какой-то фрагмент функционала, потом пишете код, который делает ваш тест зеленым, потом рефакторите.
Когда нужен рефакторинг в программировании
Существует два вида рефакторинга: плановый и при необходимости.
Рефакторинг, который изначально закладывается программистами в цикл разработки, называется плановым. Например, его могут планировать на каждые 6 месяцев или каждые 4 сплита.
В крупных компаниях, где обычно много legacy-кода, вообще формируются отдельные команды, занимающиеся исключительно рефакторингом старья. Благодаря этому остальные команды легче и быстрее понимают, что происходит в этом коде и как его использовать.
Второй вариант – рефакторинг при необходимости. К нему прибегают, когда возникают сложности с добавлением новых возможностей к старому коду. Тогда мы приостанавливаем процесс и выделяем какое-то время на переустройство того, что было.
Ресурсы для дополнительного погружения в рефакторинг
Самая известная книга о рефакторинге — это “Рефакторинг. Улучшение проекта существующего кода” Мартина Фаулера. Также есть интересное издание о рефакторинге, написанное на основе предыдущей книги — “Рефакторинг с использованием шаблонов” Джошуа Кириевски. Кстати о шаблонах. При рефакторинге всегда очень полезно знать основные паттерны проектирования приложений. В этом помогут эти отличные книги:
- “Паттерны проектирования” — авторства Эрика Фримена, Элизабет Фримен, Кэтти Сьерра, Берта Бейтса из серии Head First;
- “Читаемый код, или программирование как искусство” — Дастин Босуэлл, Тревор Фаучер.
- “Совершенный код” Стива Макконнелла, в которой изложены принципы красивого и элегантного кода.
Вам нужны специальные инструменты для рефакторинга? Мартин Фаулер говорит, что автоматизированные инструменты полезны, но не важны. Он отмечает:
Многие языки имеют IDE, которые автоматизируют стандартный рефакторинг. Это действительно ценная часть моего набора инструментов, позволяющая мне быстрее выполнять рефакторинг. Но такие инструменты не являются необходимыми — я часто работаю с языками программирования без поддержки инструментов, и в этом случае я полагаюсь на небольшие шаги и частое тестирование для обнаружения ошибок.
Многие среды разработки автоматизируют механические аспекты рефакторинга. Инструменты рефакторинга ключевого кода:
- Visual studio intellicode
- Eclipse IDE
- Spring Tool Suite 4
- Rider
- IntelliJ IDEA
- SonarQube
Каковы преимущества рефакторинга?
Рефакторинг исходного кода дает множество преимуществ. Он превращает беспорядочный, неправильный и/или повторяющийся код в чистый код. Он решает проблемы стандартизации, которые могут возникнуть, когда несколько разработчиков пишут свой собственный код. Рефакторинг обеспечивает лучшую читаемость и улучшает поддерживаемость исходного кода, а также его общую структуру и функциональность. Рефакторинг может упростить расширение кода и добавить новые функции. Удаление ненужных частей, таких как дубли, также может привести к тому, что код будет использовать меньше памяти и работать быстрее.
Например, в 2014 году инженеры Kickstarter столкнулись с проблемой экспоненциального роста числа пользователей, что привело к снижению производительности запросов. В ответ они перенесли запросы MySQL на Redis и сократили типичное время загрузки более чем на 100 мс, что привело к уменьшению дисперсии времени загрузки и в целом более быстрой работе сайта.
Представим такую ситуацию: мы открыли кафе, построили там классную кухню и наняли шеф-повара. Когда кафе только запускалось, в меню были самые простые блюда, которые можно было разогреть в микроволновке. Вы купили микроволновку и поставили на кухне. Рядом разместили стеллаж для всего нужного.
Через два месяца дела пошли лучше, и мы решили добавить в меню выпечку. Купили духовой шкаф, протянули провода, добавили рядом стойку с подносами. Место на кухне уменьшилось, повар всё время перешагивает через провода и обходит стойку, но работать можно.
Ещё через месяц поставили фритюрницу и миксер для теста. К ним тоже протянули провода, добавили рядом шкафы для утвари и наняли второго повара. В итоге на кухне полный бардак, всё друг другу мешает, а блюда готовить неудобно. Если мы захотим добавить новую плиту, то это будет уже сложно: места нет, хотя по площади можно поставить хоть две таких плиты.
В этот момент приходит кухонный проектировщик и рисует всё заново: где что должно стоять.
Через неделю на кухне всё по-другому: оборудование стоит так, чтобы не мешать поварам, провода спрятаны в короба, а стойки с оборудованием не загораживают выход. При этом меню в кафе не изменилось: посетители даже не знают, что у нас что-то происходило на кухне, потому что мы оптимизировали процессы, а не меню. Это и есть рефакторинг — когда мы меняем что-то внутри так, что снаружи это незаметно, но работать дальше становится проще.
Еще несколько правил написания кода, которыми я бы хотел закончить статью
- Используйте [] в массивах, а не старую версию array();
- Используйте === оператор вместо == в случае, когда важно проверять dataType;
- Всегда будет хорошей идеей давать открытым методам содержательные, понятные и в то же время короткие названия. У приватных методов могут быть длинные названия, так как у них ограниченный объем;
- Используйте общие названия только в методах, которые реализуют интерфейс, например, add(). Содержательные названия понадобятся единственным методам класса addUser() и addDocument();
- Убирайте неиспользованные методы из классов;
- Используйте префиксы is и has для функций, которые используют логическое выражение: isAdmin($user), hasPermission($user);
- Всегда пользуйтесь модификаторами доступа в классовых методах и параметрах;
- Остерегайтесь загрязнения интерфейса: выбирайте только те методы, которыми пользователи могут пользоваться открыто;
- Организуйте классы методов так, чтобы открытые методы были на поверхности;
- Всегда применяйте принцип единственной обязанности к своим классам.
Как избежать рефакторинга?
Скажу сразу: никак. В быстро развивающихся проектах это невозможно по причине того, что разработка очень динамичная, и согласованием архитектуры, написанием документации и тщательной технической проработкой задач зачастую пренебрегают. Как следствие, создается много низкосортного кода, «некрасивых» временных решений (которые потом становятся целевыми, ибо «работает – не трогай») и нелогичных решений, в которых последующие поколения разработчиков и аналитиков уже не могут разобраться. В проектах, где сроки терпят и могут смещаться месяцами (как правило, это крупные долгоиграющие проекты в банках, страховых и т.п.) времени, ресурсов и экспертизы на тщательное проектирование выделено больше, но здесь мешает масштаб проектов и организации. Слишком много команд работают в одном системном пространстве, их интересы зачастую не пересекаются, поэтому в разработку друг друга команды заглядывают лишь одним глазом, да и коммуникации между такими командами не всегда работают. Как итог – у каждого свой велосипед, потому что либо «не знали, что у соседей такой же велосипед, и поехать можно на нём», либо «знали, но решили на него не садиться, вдруг он сломается и тогда не доедет никто».
Таким образом, рефакторинг нужно время от времени проводить в любом проекте. Ниже приведено несколько советов, как провести рефакторинг не в ущерб текущим проектным задачам, а также что делать, чтобы минимизировать технический долг и тратить на его устранение меньше ресурсов.
Пример тестового исследования рефакторинга кода
Далее мы смоделируем потенциальный пример из реальной жизни любой продуктовой компании, когда тестировщик может и/или должен поработать с программным кодом после завершения процесса его рефакторинга.
Итак, гипотетическую задачу можно сформулировать следующим образом – выполнить рефакторинг модуля для редактирования строго закодированных переменных с последующим внесением комментариев к новому инструментарию автоматизированной генерации технической документации.
Потенциальный вызов заключается в том, что никаких серьезных задач перед тестировщиком в принципе нет. Ведь, так как модуль новый и содержит уже принятые функциональные требования, группа юнит-тестов была выполнена с первоначального запуска рефакторинга.
Были проведены повторные испытания модуля и относительно известных пострадавших участков кода. О любых багах сразу сообщалось разработчику, и они устранялись до релиза продукта.
Как видим, ничего сложного, если вовремя и правильно структурировать процесс тестирования.