Избавляемся от временных переменных

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

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

<?
	$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. измените имя временной переменной в том месте, где она первый раз объявляется и получает значение. Имя нужно подобрать говорящее, чтобы было понятно, что хранится в этой переменной;
  2. измените имя этой переменной в тех местах, где она используется до второго присвоения значения;
  3. протестируйте работу скрипта;
  4. повторите шаги 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;
	}
…
?>

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

Всего доброго!





Читайте также:




© Copyright. . I-Novice. All Rights Reserved. Terms | Site Map