Временные переменные — одни из самых распространенных источников излишней сложности в программном коде. Они накапливаются в длинных методах, дают расплывчатые имена вычисляемым значениям и затрудняют извлечение, тестирование или повторное использование содержащейся в них логики. Рефакторинг «Замена временных переменных на запросы», описанный Мартином Фаулером в книге «Рефакторинг: улучшение дизайна существующего кода» , решает эту проблему напрямую: вместо хранения вычисленного значения в локальной переменной, вы выносите вычисление в именованный метод, запрос, и вызываете его там, где требуется это значение.
В результате получается код, который передает намерение, а не скрывает его. Вычисления больше не скрыты в присваивании переменной в начале длинного метода; у них есть имя, местоположение и возможность тестирования в изоляции. В этой статье рассматривается вся техника, что такое временные переменные, когда они становятся проблемой, как шаг за шагом выполнить рефакторинг в Java, Python и TypeScript, когда его применять, а когда нет, и как он связан с другими техниками в каталоге рефакторинга.
Уверенно переработайте свой код.
SMART TS XL трассировки, где используются временные переменные, дублирующиеся вычисления и извлеченные методы запросов.
Узнать большеЧто такое временная переменная (temp) в программировании?
Временная переменная, обычно называемая temp , — это локальная переменная внутри функции или метода, которая хранит промежуточный результат для использования в той же области видимости. Она вычисляется один раз, хранится в именованной переменной, а затем на неё ссылаются позже в той же функции. Переменная существует только в течение времени выполнения вызова функции; она недоступна за её пределами и не хранится в состоянии объекта.
питон
# Python: base_price is a temp variable
def calculate_total(quantity, item_price):
base_price = quantity * item_price # temp: computed once, used below
if base_price > 1000:
return base_price * 0.95
return base_price * 0.98
Ява
// Java: basePrice is a temp variable
double basePrice = quantity * itemPrice; // temp
if (basePrice > 1000) {
return basePrice * 0.95;
}
return basePrice * 0.98;
машинопись
// TypeScript: basePrice is a temp variable
const basePrice = quantity * itemPrice; // temp
if (basePrice > 1000) return basePrice * 0.95;
return basePrice * 0.98;
Временные значения сами по себе не являются чем-то плохим. У них есть законные применения: фиксация результата дорогостоящей операции, повторение которой было бы нецелесообразным, разбивка сложных многошаговых вычислений на понятные этапы или хранение значений, накапливающихся в течение итераций цикла. Проблема возникает, когда временные значения используются рефлексивно для простых производных значений, которые было бы понятнее в виде именованных методов, или когда они накапливаются в длинном методе и заставляют читателей отслеживать несколько одновременно активных промежуточных значений.
Что такое рефакторинг в разработке программного обеспечения?
Рефакторинг — это процесс реструктуризации существующего кода без изменения его наблюдаемого поведения. Цель — улучшить внутреннее качество кода: его читаемость, тестируемость, поддерживаемость и модульность. Рефакторинг не добавляет новые функции и не исправляет ошибки; он изменяет структуру кода, сохраняя при этом его функционал.
Замена временных данных на запросы — это один из методов рефакторинга в каталоге из нескольких десятков техник, описанных Мартином Фаулером. Он относится к семейству техник, предназначенных для работы с методами, которые стали слишком длинными или слишком сложными:
| Техника рефакторинга | Что оно делает |
|---|---|
| Заменить Temp на Запрос | Выносит вычисление временной переменной в именованный метод. |
| Метод извлечения | Выделяет блок кода в новый именованный метод. |
| Встроенная температура | Заменяет простую временную переменную её выражением напрямую. |
| Разделить временную переменную | Разделяет временные данные, используемые для разных целей, на отдельные переменные. |
| Замените цикл на конвейер. | Заменяет императивный цикл функциональным конвейером (map, filter, reduce). |
| Представьте поясняющую переменную | Вводит именованный временный элемент для уточнения сложного выражения. |
Эти методы не используются изолированно. Замена временных переменных запросом, по описанию Фаулера, является важным шагом перед извлечением метода: если метод содержит временные переменные, извлечение его части в новый метод становится затруднительным, поскольку эти временные переменные могут использоваться как до, так и после извлекаемого участка. Предварительное удаление временных переменных путем преобразования их в запросы открывает путь для извлечения.
Что такое «Заменить временную переменную запросом»?
Замена временной переменной на вызов метода — это метод рефакторинга, который преобразует локальную временную переменную в вызов метода. Вместо вычисления значения и присвоения его локальной переменной, вычисление выносится в приватный метод — запрос, который возвращает вычисленное значение при вызове. Везде, где использовалась временная переменная, ссылка на нее заменяется вызовом метода запроса.
Канонический пример из книги Фаулера «Рефакторинг» :
До:
Ява
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
После:
Ява
if (basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
private double basePrice() {
return _quantity * _itemPrice;
}
Метод запроса basePrice() Теперь это именованный, самодостаточный вычислительный объект. Его можно вызвать из любого другого метода класса, протестировать независимо, переопределить в подклассах и понять без предварительного прочтения вызывающего метода.
Проблема с временными переменными
Они фрагментируют логику в рамках одного метода.
Временная переменная разделяет вычисление на две отдельные части: присваивание (где вычисляется значение) и использование (где оно считывается). В коротком методе такое разделение безвредно. В методе, разросшемся до тридцати или пятидесяти строк, присваивание и использование могут быть разделены множеством строк другой логики. Читателю приходится прокручивать страницу вверх, чтобы найти присваивание, удерживать значение в рабочей памяти и возвращаться к использованию. Каждая дополнительная временная переменная увеличивает эту когнитивную нагрузку.
Метод извлечения блоков
Наиболее существенная практическая проблема с временными переменными заключается в том, что они блокируют другие варианты рефакторинга. Рассмотрим метод со сложным условным ветвлением, который было бы полезно выделить в отдельный метод. Если ветвь использует временную переменную, которая была назначена ранее в методе, то для выделения потребуется либо передать временную переменную в качестве параметра, либо сделать её переменной экземпляра, либо пересчитать её значение внутри выделенного метода. Ни один из этих вариантов не является чистым решением. Предварительное удаление временной переменной путём замены её запросом полностью устраняет это препятствие.
Они способствуют повторному использованию и мутации.
Временные переменные иногда используются повторно для разных целей в рамках одного и того же метода, эту практику Фаулер называет «запутанностью временных переменных». Переменная с именем temp or result Переназначаемые значения, которые переназначаются несколько раз, не предоставляют никакой семантической информации и активно вводят читателей в заблуждение относительно того, что они представляют собой в любой конкретный момент. Даже временные значения, имеющие однократное назначение, могут накапливаться до тех пор, пока область применения метода не будет загромождена промежуточными значениями, которые читателям приходится отслеживать одновременно.
Пошаговая инструкция: Как применить замену временных данных на запрос.
Преобразование состоит из четырех этапов, которые можно безопасно применять на любом языке:
Шаг 1: Убедитесь, что временная переменная присваивается ровно один раз и никогда не изменяется. Если временная переменная переназначается позже в методе, сначала разделите её, используя функцию Split Temporary Variable.
Шаг 2: Выделите правую часть оператора присваивания в отдельный приватный метод. Дайте методу имя, описывающее, что он вычисляет, а не как. basePrice() лучше, чем calculateQuantityTimesPrice().
Шаг 3: Замените все ссылки на временную переменную вызовом нового метода. Большинство IDE могут сделать это автоматически: щелкните правой кнопкой мыши по временной переменной → Рефакторинг → Встроить переменную, затем выберите «Извлечь метод» для встроенного выражения.
Шаг 4: Удалите объявление временной переменной. Если извлечение завершено, временная переменная теперь не должна иметь ссылок и может быть удалена.
Java: Полный рабочий пример
Ява
// Before: Order class with temporary variables
public class Order {
private int quantity;
private double itemPrice;
public double getPrice() {
double basePrice = quantity * itemPrice; // temp 1
double discountFactor; // temp 2
if (basePrice > 1000)
discountFactor = 0.95;
else
discountFactor = 0.98;
return basePrice * discountFactor;
}
}
Ява
// After: temps extracted to query methods
public class Order {
private int quantity;
private double itemPrice;
public double getPrice() {
return basePrice() * discountFactor();
}
private double basePrice() {
return quantity * itemPrice;
}
private double discountFactor() {
return basePrice() > 1000 ? 0.95 : 0.98;
}
}
Способ getPrice() Теперь это выглядит как единое выражение, которое четко передает суть вычислений. Каждый извлеченный запрос можно читать, тестировать и расширять независимо. Обратите внимание, что discountFactor() призывы basePrice()Это правильно, потому что basePrice() Это чисто вычислительный процесс без побочных эффектов, поэтому его повторный вызов не сопряжен с риском.
Python: Замените Temp на Property
В Python естественным эквивалентом метода запроса является @property, что позволяет вызывать метод без скобок и читается идентично доступу к атрибуту:
питон
# Before: temporary variables in a method
class Order:
def __init__(self, quantity, item_price):
self.quantity = quantity
self.item_price = item_price
def get_price(self):
base_price = self.quantity * self.item_price # temp
discount = 0.95 if base_price > 1000 else 0.98 # temp
return base_price * discount
питон
# After: temps replaced with properties (query methods in Python)
class Order:
def __init__(self, quantity, item_price):
self.quantity = quantity
self.item_price = item_price
def get_price(self):
return self.base_price * self.discount_factor
@property
def base_price(self):
return self.quantity * self.item_price
@property
def discount_factor(self):
return 0.95 if self.base_price > 1000 else 0.98
. @property означает self.base_price читается идентично переменной экземпляра, что делает вызывающий код идентичным. self.base_price * self.discount_factor Абсолютно натуральный продукт. Каждое свойство можно проверить самостоятельно:
питон
def test_base_price():
order = Order(10, 150)
assert order.base_price == 1500
def test_discount_factor_high_value():
order = Order(10, 150) # base_price = 1500 > 1000
assert order.discount_factor == 0.95
def test_get_price():
order = Order(10, 150)
assert order.get_price() == 1500 * 0.95
Такой уровень тестируемости невозможен в версии, основанной на временных параметрах: внутренние вычисления base_price и discount_factor недоступны извне метода.
TypeScript: Методы запросов и геттеры
TypeScript поддерживает как запросы на основе методов, так и геттеры свойств, что соответствует шаблонам, доступным в Java и Python соответственно:
машинопись
// Before: temporary variables
class Order {
constructor(private quantity: number, private itemPrice: number) {}
getPrice(): number {
const basePrice = this.quantity * this.itemPrice; // temp
const discount = basePrice > 1000 ? 0.95 : 0.98; // temp
return basePrice * discount;
}
}
машинопись
// After: TypeScript getters replace temps
class Order {
constructor(private quantity: number, private itemPrice: number) {}
getPrice(): number {
return this.basePrice * this.discountFactor;
}
private get basePrice(): number {
return this.quantity * this.itemPrice;
}
private get discountFactor(): number {
return this.basePrice > 1000 ? 0.95 : 0.98;
}
}
Правильное именование методов запросов
Название метода запроса выполняет самую важную работу. Неправильно названное извлечение данных хуже, чем временный метод, который оно заменило, потому что оно создает непрозрачную прямую ссылку: вызывающим сторонам приходится переходить к определению метода, чтобы понять, что он делает, что сводит на нет его цель.
Хорошие названия методов запроса соответствуют следующим принципам:
Укажите, что это представляет собой, а не как это вычисляется. basePrice() Передает бизнес-концепцию. getQuantityTimesItemPrice() Это описание вычисления, а не понятия. Различие имеет значение, когда меняется само вычисление и название понятия. basePrice() Остается стабильным, даже если формула меняется.
Используйте именные группы для обозначения ценностей. Методы запросов возвращают значения; они не являются командами. discountFactor(), totalAmount(), isEligible() Следуйте общепринятой практике именования возвращаемых данных. calculateDiscount(), processAmount() следует императивному принципу команд, что сбивает с толку методы, которые выполняют только вычисления и возвращают результат.
Логические запросы следует оформлять как вопросы. isHighValue(), hasDiscount(), meetsThreshold() Сообщите, что возвращаемое значение является логическим (булевой) и что звонящий задает вопрос с ответом «да» или «нет». bool 変数名 (Именование логических переменных, запрос в данных Search Console) точно отражает эту проблему: логическим переменным и методам нужны имена, которые ясно показывают их значение в момент использования.
Сохраняйте неизменными имена для всех связанных методов. If basePrice() используется discountFactor()Последовательность в именовании говорит читателям о том, что discountFactor зависит от basePriceНесогласованное именование нарушает эту неявную документацию.
Когда применять функцию «Заменить временные параметры запросом»?
Примените этот рефакторинг, когда:
- Временная должность назначается ровно один раз и никогда не переназначается.
- Вычисление представляет собой чистое выражение: оно считывает данные из полей или параметров, но не изменяет внешнее состояние, не вызывает сетевые службы и не зависит от времени или случайности.
- Вычисления настолько сложны, что их обозначение улучшит читаемость, или же настолько просты, что временное значение будет просто лишним.
- Вы собираетесь применить метод Extract к блоку, который использует временный объект.
Наиболее распространенный идеальный сценарий — это производное значение: цена, общая сумма, скидка, отформатированная строка, условная классификация. Это значения, полностью полученные из полей объекта, без побочных эффектов, которые естественным образом должны быть свойствами объекта, а не промежуточными вычислениями внутри метода.
Когда не следует применять: Замените Temp на Query
Операции, критичные к производительности. Если вычисления затратны, то запрос к базе данных, сетевой вызов, цикл O(n²), двукратный вызов метода запроса приводят к удвоению затрат. Временный объект существует именно для того, чтобы этого избежать. В таких случаях либо оставьте временный объект на месте, либо кэшируйте метод запроса (кэшируйте результат после первого вызова):
питон
# Memoized property: computed once, cached
from functools import cached_property
class Order:
@cached_property
def expensive_validation(self):
return self.external_service.validate(self.data) # called once, cached
Операции с побочными эффектами. Если временная переменная содержит результат операции, которая должна выполняться только один раз (генерация уникального идентификатора, логирование, запись в файл), преобразование её в запрос приведёт к выполнению этой операции при каждом вызове. Это изменяет поведение программы, а не только её структуру. Не применяйте этот рефакторинг к временным переменным, содержащим побочные эффекты.
Температурные значения, накапливающиеся на протяжении итераций цикла. Временное устройство, являющееся аккумулятором в for петли, total += item.price, не является подходящим кандидатом для замены Temp на Query. Это не производное значение; это состояние, которое накапливается на протяжении итераций. Рассмотрите замену Loop на Pipeline, если проблема заключается в цикле.
Связанные методы рефакторинга
Метод Replace Temp with Query относится к семейству техник, которые в совокупности устраняют ненужную сложность в методах. Понимание этого семейства помогает разработчикам выбрать правильную технику для решения конкретной задачи:
Наиболее распространенным вариантом является Extract Method . Замена Temp на Query часто позволяет использовать Extract Method, очищая переменные, которые в противном случае потребовали бы неудобной передачи параметров между извлеченной частью и остальной частью метода.
Inline Temp — это обратная операция по отношению к введению временной переменной: она заменяет временную переменную её выражением непосредственно в коде. Используйте Inline Temp, когда временная переменная не добавляет ясности, а её выражение уже читабельно.
Функция Split Temporary Variable применяется, когда одна временная переменная используется для нескольких целей в одном и том же методе. Разделите её на отдельные переменные с именами, отражающими каждое назначение, а затем примените функцию Replace Temp with Query к любой из полученных временных переменных, используемых один раз.
Введение поясняющей переменной — это противоположный подход: если сложное выражение трудно читать, введение временной переменной с описательным именем может улучшить ясность. Этот метод и метод «Замена временной переменной запросом» находятся в противоречии, и разработчик должен оценить, какой подход улучшает конкретный код.
Замените цикл на конвейер. Рассматривается распространенная схема, в которой цикл с временным аккумулятором может быть заменен цепочкой операций конвейера.map, filter, reduce), что звучит более декларативно и легче читается.
Как SMART TS XL Поддерживает рефакторинг в масштабе предприятия.
Замена переменной `Temp` на `Query` — это локальная рефакторизация: она преобразует одну переменную в одном методе. В кодовой базе любого значительного размера более полезным вопросом является не «как применить эту рефакторизацию?», а «где именно в кодовой базе её следует применить и что при этом будет затронуто?»
SMART TS XL Предоставляет комплексный структурный анализ кодовой базы, который делает ответ на этот вопрос систематическим. Он выявляет, где одно и то же вычисление выполняется в виде временной переменной в нескольких местах, — шаблон, который предназначен для объединения метода Replace Temp with Query в единый именованный метод запроса. Он отслеживает, как используется рефакторизованный метод запроса после его извлечения, делая видимым масштаб рефакторинга до его выполнения. И он работает на разных языках: для корпоративных систем, где программы COBOL, сервисы Java и конвейеры Python работают с одними и теми же данными, статический анализ кода и анализ воздействия определить, где одно и то же логическое вычисление встречается в разных формах на разных языках, — это более глубокая форма проблемы, которую решает функция «Заменить временные значения запросом» на уровне одного языка.
Для команд, работающих над модернизация наследия, SMART TS XLАвтора визуализация зависимостей Это позволяет увидеть, где используются рефакторизованные компоненты, прежде чем вносить в них изменения, гарантируя, что выделение вычислений в метод запроса не нарушит работу вызывающих функций, ожидавших исходную структуру.
Временные переменные и самодокументирующийся код
Решение о замене временной переменной запросом в конечном итоге сводится к решению о том, что должен передавать код. Временные переменные передают информацию о реализации: это вычисление, которое я выполнил для получения этого значения. Методы запроса передают информацию о предметной области: это то, что означает это значение. В классе Order, basePrice() Это сообщает читателю, что данная концепция существует в данной области. double x = quantity * itemPrice рассказывает читателю об арифметической операции.
По мере развития кода концепции предметной области нуждаются в стабильном местоположении. Вычисление, встроенное во временный объект, может изменяться, дублироваться в нескольких методах или быть неправильно понято следующим разработчиком, который его прочитает. Именованный метод запроса может быть найден, протестирован, задокументирован и целенаправленно развиваться. Эта стабильность во всех местах, где требуется вычисление, и среди всех разработчиков, которые будут с ним работать, делает замену временного объекта на запрос не просто изменением синтаксиса. Это решение о том, как кодовая база сообщает о решаемой ею проблеме.