Рефакторинг - это изменение кода в лучшую сторону. Улучшения стремятся привести код в соответствие с SOLID принципами.

По своему масштабу рефаторинги могут проводится в границах следующего масштаба:
- Уровня метода/процедуры/функции
- Уровня отдельно взятого класса
- Уровня взаимодействия объектов
- Уровня ветвления выполнения(условные выражения)
- Уровня интерфейса классов
- Уровня иерархии классов
Классификация не полная, например, тут нет рефакторинга кода работающего параллельно.
Рефактори уровня метода/процедуры/функции.
Самый популярный рефакторинг - это Выделение метода(Extract Method).
Фактически это декомпозиция большого длинного, а посему и сложно для чтения и понимания метода. Мы выбираем несколько логически связанных операций в теле большого и сложного метода и выносим в отдельный метод. Не всякий код, который нам хочется вынести в отдельный метод поддается рефакторингу сразу. Часто бывает, что код который мы хотим вынести взаимодействует с какими то данными, как то их модифицирует. Как водится любое решение имеет как плюсы так и минусы.
Плюсы. Имя нового метода станет отличным комментарием поясняющем, что делаю вынесенные операции. Выделенный метод можно использовать в другом месте, а не copy-past-ить код, так проще вносить изменения. Исходный большой метод стал меньше и понятней.
Минусы. Мы можем получить ситуацию когда в выделенный метод придется передавать тучу параметров и возвращать специально созданные для этих целей структуры данных. Этого делать нельзя!
Метод не должен получать больше 4-5 параметров, иначе его сигнатура становится не читаемой. Так что без фанатизма, если не дается кусочек рефакторингу - то и бог с ним. Пример кода.
Встраивание метода (Inline Method).
Антипод предыдущего рефакторинга. Как известно лучшее решение всегда располагается посредине между крайностей(не обязательно двух^_^) Данный рефакторинг заключается в том, что мы телом маленького метода заменяем его вызовы везде где он используется. Применяется он в случае если метод используется редко, а его тело эквивалентно названию. Этот рефакторинг не возможен для виртуальных функций. Пример кода.
Замена временной переменной вызовом метода (Replace Temp With Query).
Часто происходит как отдельный этап выделения метода. Так как временные переменные мешают выделить метод(приходится значения этих переменных передавать как параметры), можно провести данный рефакторинг. Подразумевается, что временная переменная не изменяет свое значение. Если это не так то метод который встанет на место временной переменной должен стать зависимым от тех данных которые должны менять значение. Пример кода который все проясняет.
Встраивание временной переменной (Inline Temp).
Этот рефакторинг часто происходит как отдельный элемент предыдущего. Само по себе встречается как результат оптимизации кода, результаты расчетов кешируются во временной переменной для многократного использования, потом многократное использование выделяется в отдельный метод и временная переменная становится не нужна здесь, однако про нее забыли. Рефакторинг имеет смысл если выражение которое заменяет временную переменную легко читается и используется редко, может даже и однократно.
Мутно объяснил, если объяснил. Пример кода может помочь понять лучше.
Введение поясняющей переменной (Introduce Explaining Variable).
Используется для упрощения чтения кода. Создается переменная с именем семантически близким для этого выражения. Если выражение повторяется несколько раз, возможно, имеет смысл использовать выделение метода для этого выражения. Например, длинная цепочка вызовов через оператор доступа к члену объекта/класса (обычно точка(С#) или стрелка(С++)). Успешно применяется в условных операторах с кучей элементов в условии. Всю эту цепь условий допустимо выполнение ветки кода или нет можно рассчитать в логической переменной с именем isDataValid. Выделение метода более полезный рефакторинг, так как выделенный код становится доступен другим методам и объектам, в то время как переменная локальна. Пример кода.
Расщепление временной переменной (Split Temp Variable).
Применяется когда одной временной переменной с одним и тем же именем присваивается значения(причем эти значения носят совершенно разный семантический смысл) несколько раз и эти значения совершенно разных функций. Для много функциональной переменной более выразительное название чем temp не подберешь, уж слишком много она на себя берет. Например, величины скорости и ускорения могут хранится в одной переменной просто потому что их тип подходит для хранения обоих величин. Подобные переменные берутся от лени программистов или от излишнего стремления сэкономить место в стеке, но в наш век килобайт памяти стоит значительно дешевле времени программиста(я не говорю о специальных узких местах, где оптимизация стоит во главе). Пример кода.
Исключения:
- Переменные аккомулирующие результат вычислений в цикле.
- Переменные управления состоянием компонента.
- Итераторы.
Удаление присваиваний параметрам (Remove Assignments To Parameters).
Функция в которую передаются данные, ни в коем случае не должна присваивать новые значения этим параметрам. Эта функция может вызывать соответствующие методы у объектов переданных в качестве параметров для их изменения, но ни в коем случае не присваивать на прямую и не подменять значение параметра другим значением. Если обязательно нужно изменить значение, которое передано в параметре, следует использовать временную переменную. Все это связано с проблемой передачи данных по ссылке и не очевидностью того факта, что функция может поменять данные которые в нее передаются, а так же используются за ее пределами. Пример кода.
На мой взгляд, стоит придерживаться правила, что входные параметры неизменены, новые данные возвращаются только как результат функции. Идея та же что и в const данных, шаблоне Immutable и ReadOnly объектах.
Замена метода методом-объеком / функтором (Replace Method With Method-Object).
Представим, что у нас есть большой метод с кучей временных переменных и мы не можем сделать 'выделение метода' для его части потому, что придется все временные переменные передавать в выделяемый метод как параметры. Переменных много - выделенный метод не читаем! Как быть? Создаем целый класс для выполнении данной функции и называем его как функцию передаем в его конструктор или просто параметризуем бывшими временными переменными. Так как все временные переменные стали полями класса их не нужно передавать как параметры и можно спокойно дробить большой метод не маленькие путем 'выделения метода'. Пример кода.
Замещение алгоритма (Substitute Algorithm).
Решил задачу - написал алгоритм. Через пару недель понял, что можно написать проще и понятней - самое время переписать! Такой момент настает или когда осеняет идеей или начинаешь использовать новые удобные библиотеки. В общем когда твое понимание решения задачи эволюционирует. Этот рефакторинг делается когда дальше рефакторить уже некуда, а все равно хочется. Т.к. данный рефакторинг - это не механическая замена одного выражения другим, то самое главное требование - очень тщательно тестировать новый алгоритм на соответствие старому. Юнит тесты здесь лучшие друзья. Пример кода.
Добрый совет на последок:
Каждый раз когда читаешь код, задавай себе вопрос "что с кодом не так?", "Как код сделать более понятным?", "Что делает этот код?". Каждый раз когда изучаешь какой либо класс или отдельные методы думай как его изменить, чтоб он стал более понятным! И меняй, пусть следующий читатель не тратит кучу времени чтоб понять как это работает!
В идеале, порядок таков:
- Знать требования которые накладываются на этот участок кода(функциональные и не функциональные).
- Понять как работает код.
- Написать модульные тесты, которые покрывают все варианты работы кода. Все тесты должны успешно пройти.
- Прорефакторить код и сделать его понятным.
- Прогнать тесты и убедиться, что ничего не сломал.
Работы много, особенно лень покрывать тестами код перед рефакторингом=) Но бывают случаи когда внести изменения в систему практически невозможно не привнеся ошибку. Здесь нужно начинать с написания тестов и приступать к рефакторингу иначе дело не сдвинется с мертвой точки.
8fe22f87-6453-4502-b921-303284074e7e|0|.0
Refactoring, Architecture
refactiring, архитектура