Причины применения рефакторинга. Причины применения рефакторинга Что такое рефакторинг программного кода

28.11.2023

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

Цели рефакторинга

Цель рефакторинга - сделать код программы более легким для понимания; без этого рефакторинг нельзя считать успешным.

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

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

Причины применения рефакторинга

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

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

Признаки плохого кода

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

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

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

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

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

Выделение метода (extract method)

Выделение метода заключается в выделении из длинного и/или требующего комментариев кода отдельных фрагментов и преобразовании их в отдельные методы, с подстановкой подходящих вызовов в местах использования. В этом случае действует правило: если фрагмент кода требует комментария о том, что он делает, то он должен быть выделен в отдельный метод. Также правило: один метод не должен занимать более чем один экран (25-50 строк, в зависимости от условий редактирования), в противном случае некоторые его фрагменты имеют самостоятельную ценность и подлежат выделению. Из анализа связей выделяемого фрагмента с окружающим контекстом делается вывод о перечне параметров нового метода и его локальных переменных.

Перемещение метода (move method)

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

Замена условного оператора полиморфизмом (replace conditional with polymorphism)

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

Основные принципы:

Проблемы, возникающие при проведении рефакторинга

  • проблемы, связанные с базами данных;
  • проблемы изменения интерфейсов;
  • трудности при изменении дизайна.

Средства автоматизации рефакторинга

Технические критерии для инструментов рефакторинга.

Концепция «рефакторинга» (refactoring) возникла в кругах, связанных со Smalltalk, но вскоре нашла себе дорогу и в лагеря приверженцев других языков программирования. Поскольку рефакторинг является составной частью разработки структуры приложений (framework development), этот термин сразу появляется, когда «структурщики» начинают обсуждать свои дела. Он возникает, когда они уточняют свои иерархии классов и восторгаются тем, на сколько строк им удалось сократить код. Структурщики знают, что хорошую структуру удается создать не сразу - она должна развиваться по мере накопления опыта. Им также известно, что чаще приходится читать и модифицировать код, а не писать новый. В основе поддержки читаемости и модифицируемости кода лежит рефакторинг - как в частном случае структур (frameworks), так и для программного обеспечения в целом.

Так в чем проблема? Только в том, что с рефакторингом связан известный риск. Он требует внести изменения в работающий код, что может привести к появлению трудно находимых ошибок в программе. Неправильно осуществляя рефакторинг, можно потерять дни и даже недели. Еще большим риском чреват рефакторинг, осуществляемый без формальностей или эпизодически. Вы начинаете копаться в коде. Вскоре обнаруживаются новые возможности модификации, и вы начинаете копать глубже. Чем больше вы копаете, тем больше вскрывается нового и тем больше изменений вы производите. В конце концов, получится яма, из которой вы не сможете выбраться. Чтобы не рыть самому себе могилу, следует производить рефакторинг на систематической основе. В книге «Design Patterns» сообщается, что проектные модели создают целевые объекты для рефакторинга. Однако указать цель - лишь одна часть задачи; преобразовать код так, чтобы достичь этой цели, - другая проблема.

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

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

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

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

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

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

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

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

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

Принципы рефакторинга

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

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

Зачем нужно проводить рефакторинг?

  • Рефакторинг улучшает композицию программного обеспечения
  • Рефакторинг облегчает понимание программного обеспечения
  • Рефакторинг помогает найти ошибки
  • Рефакторинг позволяет быстрее писать программы

Когда следует проводить рефакторинг?

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

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

Почему рефакторинг приносит результаты

Из-за чего бывает трудно работать с программами? В данный момент мне приходят в голову четыре причины:

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

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

Когда рефакторинг не нужен?

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

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

Другой случай, когда следует воздерживаться от рефакторинга, это близость даты завершения проекта. Рост производительности, достигаемый благодаря рефакторингу, проявит себя слишком поздно - после истечения срока. Правильна в этом смысле точка зрения Уорда Каннингема (Ward Cunningham). Незавершенный рефакторинг он сравнивает с залезанием в долги. Большинству компаний для нормальной работы нужны кредиты. Однако вместе с долгами появляются и проценты, то есть дополнительная стоимость обслуживания и расширения, обусловленная чрезмерной сложностью кода. Выплату каких-то процентов можно вытерпеть, но если платежи слишком велики, вы разоритесь. Важно управлять своими долгами, выплачивая их часть посредством рефакторинга.

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

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

Рефакторинг играет особую роль в качестве дополнения к проектированию. Если заранее подумать об архитектуре программы, то можно избежать последующей дорогостоящей переработки. Многие считают, что проектирование важнее всего, а программирование представляет собой механический процесс. Аналогией проекта служит технический чертеж, а аналогией кода - изготовление узла. Но программа весьма отличается от физического механизма. Она значительно более податлива и целиком связана с обдумыванием. Как говорит Элистер Кокберн (Alistair Cockburn):
«При наличии готового дизайна я думаю очень быстро, но в моем мышлении полно пробелов».

Существует утверждение, что рефакторинг может быть альтернативой предварительному проектированию. В таком сценарии проектирование вообще отсутствует. Первое решение, пришедшее в голову, воплощается в коде, доводится до рабочего состояния, а потом обретает требуемую форму с помощью рефакторинга. Такой подход фактически может действовать. Мне встречались люди, которые так работают и получают в итоге систему с очень хорошей архитектурой. Тех, кто поддерживает «экстремальное программирование» [ Beck , XP ], часто изображают пропагандистами такого подхода.
Подход, ограничивающийся только рефакторингом, применим, но не является самым эффективным. Даже «экстремальные» программисты сначала разрабатывают некую архитектуру будущей системы. Они пробуют разные идеи с помощью CRC-карт или чего-либо подобного, пока не получат внушающего доверия первоначального решения. Только после первого более или менее удачного «выстрела» приступают к кодированию, а затем к рефакторингу. Смысл в том, что при использовании рефакторинга изменяется роль предварительного проектирования. Если не рассчитывать на рефакторинг, то ощущается необходимость как можно лучше провести предварительное проектирование. Возникает чувство, что любые изменения проекта в будущем, если они потребуются, окажутся слишком дорогостоящими. Поэтому в предварительное проектирование вкладывается больше времени и усилий - во избежание таких изменений впоследствии.
С применением рефакторинга акценты смещаются. Предварительное проектирование сохраняется, но теперь оно не имеет целью найти единственно правильное решение. Все, что от него требуется, - это найти приемлемое решение. По мере реализации решения, с углублением понимания задачи становится ясно, что наилучшее решение отличается от того, которое было принято первоначально. Но в этом нет ничего страшного, если в процессе участвует рефакторинг, потому что модификация не обходится слишком дорого.
Рефакторинг предоставляет другой подход к рискам модификации. Возможные изменения все равно надо пытаться предвидеть, как и рассматривать гибкие решения. Но вместо реализации этих гибких решений следует задаться вопросом: «Насколько сложно будет с помощью рефакторинга преобразовать обычное решение в гибкое?» Если, как чаще всего случается, ответ будет «весьма несложно», то надо просто реализовать обычное решение.
Рефакторинг позволяет создавать более простые проекты, не жертвуя гибкостью, благодаря чему процесс проектирования становится более легким и менее напряженным. Научившись в целом распознавать то, что легко поддается рефакторингу, о гибкости решений даже перестаешь задумываться. Появляется уверенность в возможности применения рефакторинга, когда это понадобится. Создаются самые простые решения, которые могут работать, а гибкие и сложные решения по большей части не потребуются.

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

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

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

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

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

Разработка тестов

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

Правила разработки тестов

  • Делайте все тесты полностью автоматическими, так чтобы они проверяли собственные результаты.
  • Комплект тестов служит мощным детектором ошибок, резко сокращающим время их поиска.
  • Чаще запускайте тесты. Запускайте тесты при каждой компиляции - каждый тест хотя бы раз в день.
  • Получив сообщение об ошибке, начните с создания теста модуля, показывающего эту ошибку.
  • Лучше написать и выполнить неполные тесты, чем не выполнить полные тесты.
  • Подумайте о граничных условиях, которые могут быть неправильно обработаны, и сосредоточьте на них свои тесты.
  • Не забывайте проверять, чтобы в случае возникновения проблем генерировались исключительные ситуации.
  • Опасение по поводу того, что тестирование не выявит все ошибки, не должно помешать написанию тестов, которые выявят большинство ошибок.

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

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

Признаки, что Вам нужен рефакторинга

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

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

  • Инкапсуляция поля (Encapsulate Field);
  • Выделение класса (Extract Class);
  • Выделение интерфейса (Extract Interface);
  • Выделение локальной переменной (Extract Local Variable);
  • Выделение метода (Extract Method);
  • Генерализация типа (Generalize Type);
  • Встраивание (Inline);
  • Введение фабрики (Introduce Factory);
  • Введение параметра (Introduce Parameter);
  • Подъём поля/метода (Pull Up);
  • Спуск поля/метода (Push Down);
  • Замена условного оператора полиморфизмом (Replace Conditional with Polymorphism);
  • и так далее;

George Carrette

Определение и применение рефакторинга кода

Что такое рефакторинг кода понять несложно – определений в сети достаточно много. Наиболее удачное и полное, на мой взгляд, в википедии, и не только потому, что оно исчерпывающее, но и потому, что в нем рефакторинг противопоставляется таким этапам , как оптимизация и реинжиниринг. Познакомиться с этим вариантом определения легко, набрав в строке слово "Рефакторинг" и открыв (скорее всего первую) ссылку на сайт свободной энциклопедии.

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

Когда уместно проводить рефакторинг кода?

В жизни каждой программы, по крайней мере, в жизни тех, что разрабатываются на заказ, наступает этап, когда основные заказчика, по мнению разработчика, выполнены, и программный продукт поступает на . А может быть даже в опытную эксплуатацию. В ходе тестирования, если у того, кто его проводит, руки растут из нужного места и мозги работают в правильном направлении, на разработчика начинает валиться большое число bug-ов, связанных с исключительными ситуациями, “защитой от дурака”, экстремальными объемами данных, неприемлемым быстродействием и так далее (идеально работающие программы сразу не пишутся). Разработчик старается быстро реагировать на вызовы судьбы и вносит большое количество локальных исправлений, а иногда и “заплат”, вследствие чего код теряет первоначальную стройность и сбалансированность. Вот в моменты между основными волнами наплывов претензий со стороны отдела технического контроля или просто ОТК и следует проводить рефакторинг кода: анализировать код и, используя ряд эффективных приемов, преобразовывать его к более согласованному и “прозрачному виду.” Естественно, что этап рефакторинга нельзя считать однократным.

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

Как мне кажется, отдельного внимания, помимо прочих, должны быть удостоены и те части кода, которые давно не редактировались (не были затронуты в процессе исправления ошибок или расширения функциональности), поскольку вряд ли они настолько невосприимчивы к вносимым вами изменениям, хотя и сохраняют корректное поведение. Но это уже ответ скорее не на вопрос “Когда уместно…”, а на вопрос “Где искать…”

Кому может быть полезен рефакторинг кода?


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

Во-вторых, сложные программные компоненты часто разрабатываются совместно несколькими людьми. Инструментов для организации совместной существует много: например это Rational Suite, в состав которого входит bag-tracker Clear-Quest и инструмент контроля версий программных компонентов Clear-Case. В последних версий, например, есть замечательный инструмент Team Foundation Server. Понятно, что любой “зрелый” программный компонент должен сопровождаться подробным описанием его внешних программных интерфейсов (спецификаций), но нужно также понимать, что любой опытный разработчик, зная, как пишутся такие спецификации, захочет изучить логику работы вашего компонента, анализируя непосредственно его . Вот для этих целей хорошо было бы, чтобы ваш код был “читабельным”. Таким образом, можно сделать вывод, что рефакторинг кода может помочь ускорить процесс совместной разработки программного продукта.

Рефакторинг vs Реинжиниринг?

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

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

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

Рефакторинг vs Оптимизация

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

Именование переменных, свойств, методов и функций

Данная тема не имеет прямого отношения к рефакторингу кода, но, тем не менее, оказывает немалое влияние на его читабельность. Каждый разработчик со временем вырабатывает свой стиль кодирования и, как следствие, использует свои правила именования лексем, частично или полностью заимствованные из различных нотаций. Довольно часто используют соглашения так называемой венгерской нотации . Я не буду в этой статье перечислять все соглашения этой замечательной нотации, обсуждать ее плюсы и минусы, но сделаю акцент на основном, по моему мнению, преимуществе ее использования: обозначение переменной или константы, помимо ее семантики, также отражает ее тип и . Например: m_iIndex – переменная, член класса целочисленного типа; gc_bUseCache - глобальная константа булева типа и т.д. Согласитесь, это довольно серьезно может облегчить жизнь тому, кто пытается разобраться в чужом коде. Помимо использования соглашений венгерской нотации я также рекомендую следовать следующим правилам:

  • Наименования свойств и полей класса булева типа формировать с использованием модальных и вспомогательных глаголов в соответствии с их назначением, например: Is ReadOnly , Has Value , Can Restore и т.д.;

  • Наименования методов, выполняющих какие-либо служебные, не связанные со спецификой вашей программы операции стараться формировать с использованием таких же служебных, часто используемых для обозначения смысла этих операций глаголов: Update View , Evaluate Value , Refresh Context и т.д.

  • При именовании методов стоит учитывать наименование класса и не дублировать его в наименовании этих методов, например: для обозначения методов открытия и закрытия соединения с базой данных класса DbConnection стоит выбрать имена Open и Close , а не Open Connection и Close Connection , соответственно. Исключением могут являться случаи, когда действие, выполняемое методом специфично или вторично по отношению к основному функционалу класса, например: методы работы с журналом событий Open Log и Close Log для того же класса DbConnection .

  • Если метод или свойство открытое (public), а его назначение нетривиально (в том смысле, что не может быть сформулировано одним или двумя словами типа GetResource или SaveChanges), то скорее всего не стоит его именовать “склеиванием” сокращений нескольких слов типа GetNewClntProf – такой подход уместен для внутреннего использование, но не для метода, который “торчит наружу”, являясь частью открытого интерфейса - не все поймут правильно эту аббревиатуру, не говоря уже о том, что в разных языках (естественных) различные правила сокращения слов. Лучше не жалеть места и составить наименование, как конкатенацию всех слов целиком: GetNewClientProfile .

  • Для того, чтобы в контексте метода или функции отличать ее параметры от локальных переменных (глобальные переменные и члены класса и так выделяются префиксом типа m_ , c_ , g_ и т.д.) предлагаю имена этих локальных переменных начинать с подчеркивания “_” (естественно, если это позволяет синтаксис выбранного вами языка программирования) и дальше в соответствии с венгерской нотацией.

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

Цель работы : научиться выполнять реорганизацию программного кода на основании шаблонов рефакторинга.

Ход выполнения работы

  1. Изучить теоретические сведения.
  2. Выполнить анализ программного кода разрабатываемого ПО и модульных тестов с целью выявления плохо организованного кода.
  3. Используя шаблоны рефакторинга, выполнить реорганизацию программного кода разрабатываемого ПО и модульных тестов.
  4. Проверить успешность выполнения всех модульных тестов.
  5. Выполнить описание произведенных операций рефакторинга (было-стало-шаблон рефакторинга).
  6. В случае необходимости скорректировать проектную документацию (диаграммы классов, последовательностей).
  7. Сделать выводы по результатам выполнения работы.

Требования к содержанию работы

  1. Название работы.
  2. Цель работы.
  3. Краткая формулировка индивидуального задания.
  4. Таблица изменений дизайна ПО.
  5. Скорректированные диаграммы классов, последовательностей, в случае необходимости.
  6. Выводы.

Теоретические сведения

Признаки плохо организованного кода

Это не набор четких правил, а описание мест, на которые надо обращать внимание при рефакторинге.

Дублирующийся код

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

В простейших случаях вам поможет Extract Method. При работе с наследованием используйте Pull Up Field. Если код похож, но не в точности, надо применить Extract Method, а затем Form Template Method. Если методы делают одно и то же разными способами, выберите один из них и используйте Substitute Algorithm. Если у вас есть дублирующийся код в несвязанных классах, подумайте о возможности Extract Class.

Длинный Метод

Из объектных программ, дольше всего живут программы с короткими методами. Чем длиннее процедура, тем труднее ее понять. Если у вас есть хорошее название для метода, то вам не нужно смотреть его тело.

Мы следуем эвристике: как только мы чувствуем, что надо написать комментарий, мы вместо этого пишем метод. В 99% случаев для того, чтобы сократить метод, вам достаточно будет применить Extract Method. Локальные переменные и параметры могут препятствовать вам, так что, возможно, вам понадобится Replace Temp With Query, Introduce Parameter Object и Preserve Whole Object.

Кроме комментариев, циклы и условия также являются хорошими знаками. Используйте Decompose Conditional для условий, а для циклов - выносите тело цикла в отдельный метод.

Большой класс

Когда класс делает слишком много всего, у него обычно слишком много полей.

Можно использовать Extract Class или Extract Subclass, чтобы выделить некоторые переменные. При определении того, что выносить, обращайте внимание на общность в названии переменных, а также на то использует ли класс их все одновременно.

Кроме того, класс, в котором слишком много кода, также является рассадником дублирования и хаоса. Здесь также применяйте Extract Class или Extract Subclass.

Если ваш большой класс - это GUI класс, то попытайтесь отделить поведение от данных. Вам может понадобиться Duplicate Observed Data.

Длинный список параметров

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

Используйте Replace Parameter with Method, Preserve Whole Object и Introduce Parameter Object.

Расходящееся изменение

Расходящееся изменение - это когда один класс подвержен различным изменениям.

Мы структурируем наше ПО, чтобы было легче его изменять. Если мы хотим что-то изменить, мы ищем единственное место, где мы можем сделать изменение. Если мы не можем этого сделать - мы ощущаем плохой запах. Вы можете смотреть на класс и думать: "вот эти три метода я меняю каждый раз когда подключаю новую БД, а эти четыре - при добавлении финансового инструмента". Для того, чтобы привести это в порядок, находите все, что меняется в данном частном случае и используйте Extract Class.

Хирургия дробовиком

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

Используйте Move Method и Move Field для того чтобы вынести все изменения в один класс. Если никакой класс не выглядит хорошим кандидатом, создайте новый класс.

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

Зависть по функции

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

Просто используйте Move Method, чтобы перенести его туда, где он должен быть. Если только часть метода страдает завистью, используйте сначала Extract Method.

В случае, когда метод использует данные от нескольких объектов, поместите его к объекту, в котором он заинтересован больше всего. Используйте Extract Method для изоляции зависимостей.

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

Группы данных

Если вы видите, что элементы данных всегда путешествуют вместе, объедините их в один объект. Используйте Extract Class для полей, Preserve Whole Object и Introduce Parameter Object для параметров методов.

Хороший метод проверки: если вы удалите один из элементов данных, будут ли остальные иметь смысл? Если да, то здесь должен быть рожден объект.

Одержимость примитивами

Начинающие объектные программисты обычно избегают использовать маленькие объекты для маленьких задач, таких как валюта, диапазоны, специальные строки для телефонных номеров и т.п. Вы можете выйти из пещер в цивилизованный мир объектов с помощью Replace Data Value with Object, Replace Array with Object, если это код типа, то используйте Replace Type Code with Class, Replace Type Code with Sublasses и Replace Type Code with Strategy.

Оператор Switch (Case)

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

В большинстве таких случаев лекарством является полиморфизм. Если Switch переключается по коду типа, используйте Replace Type Code with Sublasses или Replace Type Code with Strategy. Вам может понадобиться Extract Method и Move Method чтобы изолировать Switch и поместить его в нужный класс. Когда вы настроите структуру наследования, используйте Replace Conditional with Polymorphism.

Параллельные иерархии наследования

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

Ленивый класс

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

Спекулятивное обобщение

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

Если у вас есть абстрактные классы, которые ничего не делают, используйте Collapse Hierarchy. Ненужная делегация может быть удалена с помощью Inline Class. Методы с неиспользуемыми параметрами должны быть подвергнуты Remove Parameter.

Временное поле

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

Используйте Extract Class, чтобы сделать дом для бедных сироток. Поместите туда весь код, относящийся к этим полям. Также вы можете избавиться от условного кода с помощью Introduce Null Object, для создания альтернативного объекта на случай, когда эти поля не имеют смысла.

Цепочка сообщений

Если вы видите код, когда клиент просит объект у другого объекта, затем у этого объекта следующий объект и т.п. Это может быть длинная строка getThis методов или последовательность временных переменных. В любом случае, при подобных вызовах, мы явно предполагаем знание о структуре объектов в цепочке. Любое изменение во взаимосвязях повлечет изменение этого кода. Здесь надо использовать Hide Delegate. Посмотрите, зачем используется результат вызова. Используйте Extract Method и затем Move Method , чтобы продвинуть его дальше по цепочке. Некоторые выступают против любой цепочки методов. Мы выступаем за умеренность.

Посредник

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

Однако, это может зайти слишком далеко. Вы смотрите на интерфейс класса и видите, что половина его методов просто делегируют вызовы другим классам. Время использовать Remove Middle Man и напрямую говорить с объектами, которые знают что происходит. Если только некоторые методы делегируют, то напустите на них Inline Method. Если кроме делегации добавляется поведение, используйте Replace Delegation with Inheritance.

Неоправданная близость

Иногда классы становятся слишком близки и проводят слишком много времени с приватными частями друг друга.

Используйте Move Method и Move Field для разделения частей и уменьшения близости. Посмотрите, нельзя ли организовать Change Bidirectional Association to Unidirectional. Если у классов действительно есть общий интерес, используйте Extract Class, чтобы положить общие части в одно место.

Наследование часто ведет к неоправданной близости. Потомки всегда знают о предках больше, чем хотелось бы предкам. Используйте Replace Inheritance with Delegation, когда пора покидать дом.

Неполный библиотечный класс

Зачастую библиотечный класс не совсем нас устраивает, но мы не можем применить что-то типа Move Method к нему. Если вам надо добавить пару методов, используйте Introduce Foreign Method. Если добавлять надо много, вам понадобится Introduce Local Extension.

Класс для данных

Бывают классы, которые содержат только поля и методы для доступа к ним. Это просто тупые контейнеры для данных и скорее всего, другие классы слишком детально ими манипулируют. Во-первых, примените Encapsulate Field и Encapsulate Collection, пока никто не видел. Посмотрите, где данные класса используются. Попробуйте применить Move Method, чтобы перенести этот код в сам класс данных. Классы для данных как дети. Они нужны на начальных этапах. Но чтобы вести себя как взрослый объект, они должны брать на себя ответственность.

Отвергнутое наследство

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

Традиционный ответ - иерархия плохая. Вам нужно создать новый порожденный класс и применить Push Down Method и Push Down Field. Но по нашему саркастическому использованию слова традиционный, вы догадались, что мы не будем это всегда советовать. Мы используем наследование для повторного использования. Оно сопровождается плохим запахом, но обычно он не очень силен. Если отвергнутое наследство представляет собой проблему, то воспользуйтесь традиционным советом.

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

Цели рефакторинга

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

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

Какой код надо рефакторить

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

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

    В вашей программе есть очень длинные методы/функции. Как правило, человек не может полностью воспринимать и оценивать правильность кода, если этот код занимает больше 2-3 десятков строк. Такие методы и функции следует разделять на несколько более мелких и делать одну общую функцию, которая будет последовательно вызывать эти методы. Никогда не пытайтесь сократить длину кода записывая по несколько операторов в одной строке!!! Это один из самых худших вариантов организации программы и я вам даю 100% гарантию, что такой код в итоге приведёт к ошибкам!

    Длинный список параметров функции/метода/конструктора. Большое количество параметров обычно не только усложняет понимание того, что, что делает этот метод или функция, но и усложняет понимание кода, использующего эти функции. Если Вам реально нужно, что бы функция принимала очень много параметров – просто вынесите эти параметры в отдельную структуру (либо класс), дав этой структуре разумное и понятное имя, и передавайте функции ссылку (либо указатель) на объект этой структуры или класса.

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

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

    Много “беспорядочно” хранящихся данных, которые связаны логически и их можно было бы объединить в структуру, либо класс. Логически связанные данные всегда стоит хранить в структурах/классах, даже если это всего 2-3 переменных – хуже от этого никому не станет, а вот код станет значительно понятнее.

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

    Старайтесь не хранить слишком много глобальных переменных. Если же Вам и в самом деле нужно именно столько и никак не меньше глобальных объектов – попробуйте хотя бы сгруппировать их в структуры/классы, либо хотя бы просто вынесите их в отдельный namespace – тогда шанс того, что вы случайно используете какую-то переменную по ошибке, станет значительно ниже.

© wegonnaplay.ru, 2024
Wegonnaplay - Игры каждый день