Избавляемся от временных переменных
Этой статьей я начинаю рассматривать различные приемы рефакторинга. Сегодня рассмотрим то, как правильно обращаться с переменными, а именно: как избавиться от временных переменных.
Рассмотрим следующую ситуацию: у нас есть временная переменная, которая принимает больше чем одно значение (значение присваивается ей не однократно). При этом допустим, что эта переменная не имеет отношения к циклу:
<? $temp = 2 * ($height + $width); echo $temp; $temp = $height * $width; echo $temp; ?>
Теперь сформулируем правило: каждое присвоение у каждой переменной должно осуществляться один раз. Это правило не касается цикловых переменных и переменных, которые коллекционируют какое-то значение:
<? $k = 1; for ($c = 0; $c < 10; $c++) { $k = $k * 2; // $k - коллекционная переменная // $c - цикловая переменная } ?>
Следуя нашему правилу, перепишем первый пример:
<? $perimeter = 2 * ($height + $width); echo $perimeter; $area = $height * $width; echo $area; ?>
Если одной и той же переменной в определенном куске кода присваиваются разные значения, значит, эта переменная в разные моменты времени имеет разные смыслы. Согласитесь: использование таких переменных сильно сбивает с толку того, кто читает код.
А в общем порядок работы над кодом при проведении данного приема рефакторинга можно свести к следующему алгоритму:
- измените имя временной переменной в том месте, где она первый раз объявляется и получает значение. Имя нужно подобрать говорящее, чтобы было понятно, что хранится в этой переменной;
- измените имя этой переменной в тех местах, где она используется до второго присвоения значения;
- протестируйте работу скрипта;
- повторите шаги 1-3 для остальных присвоений значений этой временной переменной.
Как видите, лучше делать это поэтапно, чтобы не пропустить и не оставить без внимания ни одно присвоение и использование временной переменной.
Для первого шага этого алгоритма я еще раз повторю: если переменная в последующих присвоениях имеет вид вроде: $i = $i + [выражение] - то эту переменную рефакторить не надо - это коллекционная переменная. Мы прекрасно знаем, что обычно к такой переменной прибавляются какие-то значения, присоединяется строка и т.п. в зависимости от ситуации, т.е. здесь ее переименование, наоборот, может осложнить чтение кода.
Пример
Представим, что у нас есть мяч, который мы пинаем два раза: сначала с одной силой, а потом по истечении какого-то промежутка времени - с другой. Мы хотим вычислить путь, пройденный в определенное время после первого пинка. У нас есть некий метод getTraversedPath какого-то класса (неважно какого):
<? … function getTraversedPath($time) { $accel = $this->first_force / $this->mass; $first_time = min($time, $this->delay); $result = 0.5 * $accel * $first_time * $first_time; $second_time = $time - $this->delay; // наше время минус время второго пинка if ($second_time > 0) { $first_vel = $accel * $this->delay; $accel = $this->second_force / $this->mass; $result += $first_vel * $second_time + 0.5 * $accel * $second_time * $second_time; } return $result; } … ?>
Я специально привел такой пример, чтобы в нем трудно было с первого раза разобраться. В этом примере испольуются законы механики и движения, чтобы получить путь, пройденный мячом в определенную точку времени.
Теперь заметим, что в этом коде переменной $accel присваивается значение выражения два раза. Первый раз - когда мы вычисляем начальное ускорение по второму закону Ньютона (можете не брать в голову, если не знаете ), второй раз - когда вычисляем ускорение, приданное мячу при втором пинке по нему (с другой силой). Эту переменную можно назвать временной и она требует разбиения ее на две переменной.
Сначала разберемся с первым присвоением. Переименуем переменную $accel в $first_accel в первом присвоении ей значения и переименуем $accel везде до второго присвоения:
<? … function getTraversedPath($time) { $first_accel = $this->first_force / $this->mass; $first_time = min($time, $this->delay); $result = 0.5 * $first_accel * $first_time * $first_time; $second_time = $time - $this->delay; if ($second_time > 0) { $first_vel = $first_accel * $this->delay; $accel = $this->second_force / $this->mass; $result += $first_vel * $second_time + 0.5 * $accel * $second_time * $second_time; } return $result; } … ?>
После этих модификаций мы должны проверить, работает ли наша программа. Уверен, что работает. Тогда переименуем $accel во втором месте. При этом имя $accel должно исчезнуть из тела метода насовсем:
<? … function getTraversedPath($time) { $first_accel = $this->first_force / $this->mass; $first_time = min($time, $this->delay); $result = 0.5 * $first_accel * $first_time * $first_time; $second_time = $time - $this->delay; if ($second_time > 0) { $first_vel = $first_accel * $this->delay; $second_accel = $this->second_force / $this->mass; $result += $first_vel * $second_time + 0.5 * $second_accel * $second_time * $second_time; } return $result; } … ?>
Вы скорее всего думаете, что в этом коде еще многое можно отрефакторить. Это правильно. Но я не буду забегать вперед и остановлюсь пока на этом.
Всего доброго!