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