Книга: Рефакторинг. Зачем?

Преобразование одной большой функции в две маленькие

Преобразование одной большой функции в две маленькие

Сейчас мы добрались до куда более сложной и куда менее однозначной темы. Дело в том, что человеческое восприятие так устроено, что анализировать сразу большой объём информации ему крайне сложно. Именно по этому, книги принято разбивать на главы, а сами главы на абзацы.

В программировании всё обстоит немного иначе и это может быть причиной путаницы. Тут текст разбит на функции (процедуры, методы, классы, сейчас это не так важно), а основное назначение функций — это возможность их повторного использования. То есть по сути, с их помощью суммарный объём кода уменьшается, они позволяют избежать многократного повторения одних и тех же фрагментов кода и тому подобное.

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

type
TRect = record
Left: Integer;
Right: Integer;
Top: Integer;
Bottom: Integer;
end;
function RectsLength(Rects: array of TRect): Integer;
var
I: Integer;
Width, Height: Integer;
begin
Result:= 0;
for I:= 0 to Length(Rects) — 1 do
begin
 Width:= Rects[I].Right — Rects[I].Left;
Height:= Rects[I].Bottom — Rects[I].Top;
 Result:= Result + 2 * Width + 2 * Height;
end;
end;

Выше приведён простой пример, который рассчитывает сумму периметров прямоугольников в массиве. Пока он не выглядит сильно сложным, но, предположим, что задача немного изменилась и нам сказали не учитывать прямоугольники площадью меньше некоего числа MinLength.

function RectsLength(Rects: array of TRect; MinLength: Integer): Integer;
var
I: Integer;
Width, Height, Len: Integer;
begin
Result:= 0;
for I:= 0 to Length(Rects) — 1 do
begin
 Width:= Rects[I].Right — Rects[I].Left;
Height:= Rects[I].Bottom — Rects[I].Top;
Len:= 2 * Width + 2 * Height;
if Len >= MinLength then
 Result:= Result + Len;
end;
end;

Не скажу, что намного сложнее, но взгляд спотыкается. А если бы мы сразу выделили функцию RectLenght, считающую площадь отдельного прямоугольника вышло бы несколько проще:

function RectLength(Rect: TRect): Integer;
var
Width, Height: Integer;
begin
 Width:= Rect. Right — Rect. Left;
Height:= Rect. Bottom — Rect. Top;
Result:= 2 * Width + 2 * Height;
end;
function RectsLength(Rects: array of TRect; MinLength: Integer): Integer;
var
I: Integer;
Len: Integer;
begin
Result:= 0;
for I:= 0 to Length(Rects) — 1 do
begin
Len:= RectLength(Rects[I]);
if Len >= MinLength then
Result:= Result + Len;
end;
end;

И пусть вас не смущает, что в сумме кода стало немного больше. Мне ещё ни разу не приходилось жалеть о такого рода рефакторинге. То есть были, конечно, случаи, когда он был не оправдан и только путал… У каждого бывали ошибки. Но никогда проблемы не были связаны с увеличением суммарного объема кода. Иногда, даже если вы смогли разделить функцию на две функции того же размера (суммарное увеличение кода в два раза), но, при этом хорошо разделили их логически — это бывает оправдано.

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

Оглавление книги


Генерация: 3.417. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз