Рефакторинг: разделение функции
Рассмотрим сегодня такую технику рефакторинга, как разделение функции на две части: одну - для модификации чего-то, вторую - для возвращения значения. Т.е. если у нас есть функция, которая и возвращает результат, и что-то делает с переданным параметром, то лучше ее разделить на две функции, одна из которых что-то возвращала бы, а вторая - что-то принимала бы и выполняла бы какие-то видимые действия.
Назовем первую функцию - запрос, а вторую - модификатор.
Вы наверно заметили, что двумя предложениями выше я упомянул о неких «видимых» действиях. О них станет понятнее в примере, который я приведу ниже.
Вообще, функция без каких-то видимых действий является более чистой, чем функция, вызов которой может вызвать еще какую-то функцию. В первом случае мы можем вызывать функцию везде, где угодно и использовать ее возвращаемое значение, при этом не волнуясь о том, что она вызовет еще кого-то. Но если она все-таки вызывает какую-то другую функцию, то она уже не чистая и придется разделить ее на две части.
В общем, правило для этой техники можно сформулировать так: функция, которая возвращает какое-то значение (т.е. функция-запрос), не должна иметь каких-либо видимых эффектов (т.е. не должна вызывать функции-модификаторы).
Порядок проведения этой техники рефакторинга
- Создать функцию, которая возвращает то же самое значение, что и оригинальная функция, которую мы разбиваем. При этом нужно обязательно посмотреть, как оригинальная функция получает возвращаемое значение, и сделать то же самое в новой функции-запросе.
- Изменить оригинальную функцию таким образом, чтобы она возвращала результат вызова функции-запроса, созданной на первом шаге. Т.е. каждая инструкция return в оригинальной функции должна выглядеть примерно так: return newQuery().
- Протестировать работу программы.
- Заменить каждый вызов оригинальной функции вызовом функции-запроса. Поместить вызов оригинальной функции перед вызовом функции-запроса. Протестировать работу программы.
- Удалить все инструкции 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 Но так бывает не всегда.
Удачи!