Рефакторинг: разделение функции

Рассмотрим сегодня такую технику рефакторинга, как разделение функции на две части: одну - для модификации чего-то, вторую - для возвращения значения. Т.е. если у нас есть функция, которая и возвращает результат, и что-то делает с переданным параметром, то лучше ее разделить на две функции, одна из которых что-то возвращала бы, а вторая - что-то принимала бы и выполняла бы какие-то видимые действия.

Назовем первую функцию - запрос, а вторую - модификатор.

Вы наверно заметили, что двумя предложениями выше я упомянул о неких «видимых» действиях. О них станет понятнее в примере, который я приведу ниже.

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

В общем, правило для этой техники можно сформулировать так: функция, которая возвращает какое-то значение (т.е. функция-запрос), не должна иметь каких-либо видимых эффектов (т.е. не должна вызывать функции-модификаторы).

Порядок проведения этой техники рефакторинга

  1. Создать функцию, которая возвращает то же самое значение, что и оригинальная функция, которую мы разбиваем. При этом нужно обязательно посмотреть, как оригинальная функция получает возвращаемое значение, и сделать то же самое в новой функции-запросе.
  2. Изменить оригинальную функцию таким образом, чтобы она возвращала результат вызова функции-запроса, созданной на первом шаге. Т.е. каждая инструкция return в оригинальной функции должна выглядеть примерно так: return newQuery().
  3. Протестировать работу программы.
  4. Заменить каждый вызов оригинальной функции вызовом функции-запроса. Поместить вызов оригинальной функции перед вызовом функции-запроса. Протестировать работу программы.
  5. Удалить все инструкции return из оригинальной функции (т.е. все инструкции return должны ничего не возвращать - просто return;). Этим шагом мы сделаем из оригинальной функции функцию-модификатор.

Пример

Пусть у нас есть функция, которая говорит нам имя человека, которого не должна пропустить система безопасности, если она находит его в массиве. При этом она посылает предупреждение охраннику.

	function findBadPerson($people) {
		foreach ($people as $person) {
			if ($person == 'Mike') {
				sendAlert();
				return 'Mike';
			}
			if ($person == 'Steve') {
				sendAlert();
				return 'Steve';
			}
		}
		return '';
	}

Эта функция вызывается следующей функцией:

	function checkPeople($people) {
		$person = findBadPerson($people);
		someCode($person);
	}

Как видим, функция findBadPerson возвращает значение и вызывает перед этим функцию sendAlert. Наша задача - разделить поиск плохого человека от посылки предупреждения охраннику. Следуя порядку, сделаем такую же функцию, что и findBadPerson, но которая не будет вызывать sendAlert:

	function findPerson($people) {
		foreach ($people as $person) {
			if ($person == 'Mike') {
				return 'Mike';
			}
			if ($person == 'Steve') {
				return 'Steve';
			}
		}
		return '';
	}

Теперь каждую инструкцию в оригинальной функции, т.е. в функции findBadPerson, мы должны заменить на return findPerson($people):

	function findBadPerson($people) {
		foreach ($people as $person) {
			if ($person == 'Mike') {
				sendAlert();
				return findPerson($people);
			}
			if ($person == 'Steve') {
				sendAlert();
				return findPerson($people);
			}
		}
		return findPerson($people);
	}

Теперь функция checkPeople будет выглядеть так:

	function checkPeople($people) {
		findBadPerson($people);
		$person = findPerson($people);
		someCode($person);
	}

В этой функции, как видите, нас уже не интересует возвращаемое значение findBadPerson, поэтому мы можем применить шаг 5:

	function findBadPerson($people) {
		foreach ($people as $person) {
			if ($person == 'Mike') {
				sendAlert();
				return;
			}
			if ($person == 'Steve') {
				sendAlert();
				return;
			}
		}
	}

Теперь переименуем имя функции-оригинала findBadPerson в sendAlertMsg():

	function sendAlertMsg($people) {
		foreach ($people as $person) {
			if ($person == 'Mike') {
				sendAlert();
				return;
			}
			if ($person == 'Steve') {
				sendAlert();
				return;
			}
		}
	}

В итоге мы получили две функции: sendAlertMsg - функция-модификатор, findPerson - функция-запрос. Но у нас есть много повторяющегося кода, который мы можем убрать с помощью приема замещения алгоритма. В итоге функция checkPeople примет окончательный вид:

	function checkPeople($people) {
		if (!empty(findPerson($people))) {
			sendAlert();
		}
		someCode($person);
	}

В итоге получилось, что мы вообще избавились от функции sendAlertMsg :) Но так бывает не всегда.

Удачи!





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




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