Рефакторинг представляет собой процесс такого изменения программной системы, при котором не меняется внешнее поведение кода, но улучшается его внутренняя структура. Это способ систематического приведения кода в порядок, при котором шансы появления новых ошибок минимальны. В сущности, при проведении рефакторинга кода улучшается дизайн кода после его написания.
Без рефакторинга композиция программы приходит в негодность. По мере внесения в код изменений, связанных с реализацией краткосрочных целей или производимых без полного понимания организации кода, последний утрачивает свою структурированность. Разобраться в проекте, читая код, становится все труднее. Рефакторинг напоминает наведение порядка в коде. Убираются фрагменты, оказавшиеся не на своем месте. Утрата кодом структурности носит кумулятивный характер. Чем сложнее разобраться во внутреннем устройстве кода, тем труднее его сохранить и тем быстрее происходит его распад. Регулярно проводимый рефакторинг помогает сохранять форму кода.
Понимание работы программного обеспечения один из навыков любого программиста, так как умение понимать код позволяет быстрее и качественнее с ним работать. Также за частую требуется работать с кодом, который был написан ранее другим программистом. А разбор неизвестного кода может занять довольно большое количество времени, что может отрицательно сказываться как на работе самого программиста, так и на работе всего предприятия. Рефакторинг помогает сделать код более легким для чтения. При проведении рефакторинга можно добиться того, что код станет лучше информировать о своей цели.
Лучшее понимание кода помогает выявить ошибки. При проведении рефакторинга приходиться читать код, понимать суть его поведения. При детальном изучении кода можно увидеть ошибки допущенный при его написании. Рефакторинг помогает в написании более надежного кода.
Рефакторинг должен проводиться по возможности как можно чаще, так как он влияет на качество написанного кода и в целом работоспособности программного обеспечения, но не стоит делать его постоянно. Далее представлены наиболее частые ситуации, когда стоит применять рефакторинг.
Проведение рефакторинга при добавлении новой функции позволяет довольно ускорить процесс разработки, за счет получения более полной информации о работе программы, а также позволяет понять на сколько возможно добавление новой функции в уже существующий код без внесения в него существенных изменений.
При исправлении ошибок польза рефакторинга во многом заключается в том, что код становится более понятным. В процессе рефакторинга изучается структура и поведение программы. За счет этого можно узнать, где находится ошибка в коде.
Благодаря разбору кода знания становятся достоянием всей команды разработчиков. Разборы помогают большему числу людей разобраться с большим числом аспектов крупной программной системы.
В некоторых случаях рефакторинг вообще не нужен. Основной пример - необходимость переписать программу с нуля. Иногда имеющийся код настолько запутан, что подвергнуть его рефакторингу, конечно, можно, но проще начать все с самого начала. Явный признак необходимости переписать код - его неработоспособность. Это обнаруживается только при его тестировании, когда ошибок оказывается так много, что сделать код устойчивым не удается.
Другой случай, когда следует воздерживаться от рефакторинга, это близость даты завершения проекта. Рост производительности, достигаемый благодаря рефакторингу, проявит себя слишком поздно – после истечения срока. Зачастую проведение рефакторинга приводит к росту производительности труда. Нехватка времени обычно сигнализирует о необходимости рефакторинга.
Рефакторинг играет особую роль в качестве дополнения к проектированию. Если заранее подумать об архитектуре программы, то можно избежать последующей дорогостоящей переработки.
При проведении проектирования, лучше сделать акцент на проведение рефакторинга, так как данный способ разработки программного обеспечения позволяет отказаться от создания сложных структур обладающих гибкостью. Так как гибкость достигается за счет проведения рефакторинга, что упрощает не только процесс разработки, но и последующее обслуживание и модификацию программы.
Рефакторинг позволяет создавать более простые проекты, не жертвуя гибкостью, благодаря чему процесс проектирования становится более легким и менее трудозатратным.
Простейшая задача с дублированием кода возникает, когда одно и то же выражение присутствует в двух методах одного и того же класса или в двух разных классах. В этом случае надо лишь выделить новый метод или класс и вызвать его из предшествующих двух точек.
Использование длинных методов ведет к тому, что возникает сложность в последующем изучении его работы. Использование декомпозиции методов позволяет упростить понимание кода за счет разбиения его на функциональные составляющие. При использовании декомпозиции стоит отметить то, что правильное задание имен методов тоже имеет существенный вес в понимании кода.
Большая длинна класса как и большая длина метода является отрицательным фактором в работе программы, так как в большом количестве строк весьма трудно находить ошибки, но довольно легко создавать их. При работе с большим классом стоит отметить, что класс можно разбить на несколько классов в зависимости от методов, которые они будут содержать, а также от атрибутов, которые будут в них передаваться.
Суть данной проблемы в коде является передача большого количества параметров для каждого метода, в которых трудно разбираться, они в последствие становятся противоречивыми и сложными в использовании, а также их приходится постоянно изменять по мере того, как возникает необходимость в новых данных. Если передавать объекты, то изменений требуется мало, потому что для получения новых данных можно использовать несколько запросов.
Также можно получить данные в одном параметре путем вызова метода объекта, который уже известен. Этот объект может быть полем или другим параметром.
Есть важное исключение, когда такие изменения не нужны. Оно касается ситуации, когда нет причины создавать зависимость между вызываемым и более крупным объектами. В таких случаях разумно распаковать данные и передать их как параметры. Если список параметров оказывается слишком длинным или модификации слишком частыми, следует пересмотреть структуру зависимостей.
Расходящиеся модификации имеют место тогда, когда один класс часто модифицируется различными способами по разным причинам. Например в случае, когда при добавлении базы данных меняется часть методов, а при появлении нового функционала остальные методы, то стоит иметь на такой случай два класса. Благодаря этому каждый класс будет иметь свою четкую зону ответственности и изменяться в соответствии с изменениями в этой зоне.
«Стрельба дробью» - это одно изменение, затрагивающее много классов. В обоих случаях желательно сделать так, чтобы в идеале между частыми изменениями и классами было взаимно однозначное отношение.
В такой ситуации следует по возможности перенести все изменяющиеся поля и методы в один класс уже существующий, или создать новый в зависимости от ситуации, чтобы внести изменения во все части кода, которые необходимо изменить.
Суть данной проблемы в том, что метод может работать с данными из другого класса, а класс, в котором он находится не задействован в методе. В данном случае лучше всего перенести метод в другой класс. Также как один из вариантов может послужить декомпозиция метода и размещение новых методов в разных местах кода.
Иногда данные могут быть объединены в группы по несколько элементов или встречаться в разных местах, например в поля в нескольких классах или в сигнатурах методов. В данном случае стоит выделить данные группы данных в отдельный класс. В результате сразу удается укоротить многие списки параметров и упростить вызов методов.
В данном случае речь идет о том, что при выполнении небольших функций не используются объекты, а используются переменные. В данном случае лучше всего объединять данные в классы или под классы, это обеспечит надежность при работе с данными, за счет определения их структуры и централизованного местоположения.
Одним из очевидных признаков объектно-ориентированного кода служит сравнительная немногочисленность операторов типа switch. Проблема, обусловленная применением switch, по существу, связана с дублированием. Часто один и тот же блок switch оказывается разбросанным по разным местам программы. При добавлении в переключатель нового варианта приходится искать все эти блоки Switch и модифицировать их.
Как правило, заметив блок switch, следует определить, где должен происходить полиморфизм. Часто переключатель работает в зависимости от кода типа. Необходим метод или класс, хранящий значение кода типа. Поэтому воспользуйтесь выделением метода для выделения переключателя, а затем перемещением метода для вставки его в тот класс, где требуется полиморфизм.
Данная проблема возникает если класс стал не нужен, или не эффективен, или не выполняет свои функции в следствие рефакторинга или модификаций кода. В данном случае остается только ликвидировать данный класс, но стоит помнить, что функционал, который он должен выполнять, должен выполняться в других классах.
Данная проблема получается в процессе написания кода с частичным функционалом, который будет использоваться в будущем, но не использующийся в настоящем. Такой подход дает определенное преимущество в дальнейшей разработке за счет превентируемой гибкости, но при этом возникает проблема на стадии поддержки и модификации кода, так как трудно понять поведение кода, который не используется.
У методов с параметрами, которые не используются, стоит удалить данные параметры. Методы с именами, которые не несут смысловой нагрузки в названии стоит переименовать.
Иногда обнаруживается, что в некотором объекте атрибут устанавливается только при определенных обстоятельствах. Такой код труден для понимания, поскольку естественно ожидать, что объекту нужны все его переменные. Можно сломать голову, пытаясь понять, для чего существует некоторая переменная, когда не удается найти, где она используется.
В качестве варианта решения проблемы можно вынести все переменные в отдельный класс вместе с кодом, который работает с ними.
Цепочки сообщений появляются, когда клиент запрашивает у одного объекта другой, у которого клиент запрашивает еще один объект, у которого клиент запрашивает еще один объект и т. д. Это может выглядеть как длинный ряд методов или последовательность временных переменных. Такие последовательности вызовов означают, что клиент связан с навигацией по структуре классов. Любые изменения промежуточных связей означают необходимость модификации клиента.
Обычно лучше посмотреть, для чего используется конечный объект. Попробуйте с помощью выделения метода взять использующий его фрагмент кода и путем перемещения метода передвинуть его вниз по цепочке. Если несколько клиентов одного из объектов цепочки желают пройти остальную часть пути, добавьте метод, позволяющий это сделать.
Одной из главных характеристик объектов является инкапсуляция - сокрытие внутренних деталей от внешнего мира. Инкапсуляции часто сопутствует делегирование. Иногда возникает ситуация, когда довольно большое количество методов делегирует обработку другому классу. В данном случае стоит избавиться от посредника и перенести некоторые методы в вызывающий класс. Также в некоторых случая можно заменять делегирование наследованием.
Иногда классы оказываются в слишком близких отношениях и чаще, чем следовало бы, погружены в закрытые части друг друга. Данные классы стоит разделить, для этого стоит применить перемещения методов и полей. Также общую часть можно выделить в отдельный класс.
Для решения данной задачи возможно переименование методов, выполняющих одинаковые действия, но различающимся сигнатурами. Возможно применение выделение и перемещение метода, а при избыточном перемещении кода возможно применение выделение родительского класса.
Проблема в том, что обычно оказывается невозможным модифицировать библиотечный класс, чтобы он выполнял какие-то желательные действия.
Для этой работы у нас есть пара специализированных инструментов. Если в библиотечный класс надо включить всего лишь один-два новых метода, можно выбрать введение внешнего метода.
Такие классы содержат поля, методы для получения и установки значений этих полей и ничего больше. Такие классы - бессловесные хранилища данных, которыми манипулируют другие классы.
На ранних этапах в этих классах могут быть открытые поля, и тогда необходимо закрыть эти поля, а при наличии коллекций закрыть и их.
Посмотрите, как эти методы доступа к полям используются другими классами, в данном случае стоит переместить методы доступа в класс данных.
Подклассам полагается наследовать методы и данные своих родителей. Но не все методы и данные, которые наследуются, используются в дочерних классах. В данном случае стоит изменить код таким образом, чтобы родительский класс содержал по возможности только общие для всех подклассов данные и методы.
Комментарии довольно неплохой способ указать на структуру и функции выполняемые в коде, но не редко они могут носить противоречивый характер после изменения кода. Комментарии, несомненно, необходимы для большого количества кода, но они должны быть использованы обоснованно.
Если для объяснения действий блока требуется комментарий, то возможно стоит заменить комментарий на переименование класса, метода, констант и переменных. Также можно в коде выделять методы, которые будут понятны без комментария.
1.
Выделение метода (преобразуйте фрагмент кода в метод, название
которого объясняет его значение).
Длинный метод разбиваем на логические под-методы.