Рефакторинг. Зачем? - DarkGoodWIN
Шрифт:
Интервал:
Закладка:
2. Попытайьесь ещё до того, как начнёте писать код, разбить большое действие на составляющие. Банальный пример, о котором мы уже говорили — рассчёт суммы периметров. Его очень просто разбить на два действия — расчёт периметра одного поямоугольника и вычисление суммы этих величин.
3. Если объединить этот пункт с предыдущим, можно сформулировать такой приём программирования, как использование ещё не существующих функций.
begin
Rect:= ПолучитьПрямоугольник;
Point:= ПолучитьТочку;
if ТочкаВПрямоугольнике(Point, Rect) then
ДобавитьТочку(Point);
end;
В приведённом примере, функции с названиями на русском языке не существуют в момент написания фрагмента (в реальном программировании стоит использовать те названия, которые будут работать в вашей среде программирования и которые вы собираетесь оставить насовсем, это просто пример, я в своей работе стараюсь именовать функции и переменные так, чтобы это было понятно англоговорящим представителям рода человеческого).
Сам устыдился своих упрёков и решил перевести в тот код, который сам бы и написал:
begin
Rect:= GetRect;
Point:= GetPoint;
if PointOnRect(Point, Rect) then
AddPoint(Point);
end;
Так вот, несуществующие функции — это не проблема. Во–первых — каждую из них гарантированно проще написать, чем исходную функцию целиком. Во–вторых, компилятор вам подскажет, если вы забыли реализовать какую–то из них. В случае, если вы забыли какое–то действие в основном коде, вы можете это не заметить. Ну и в-третьих — вы сразу делаете код понятным, вам не приходится делать два дела вместо одного.
Пользуясь случаем, приведу ещё один довод в пользу коротких функций. Как правило, они более конкретны. То есть не «делают то, то и ещё это за одно», а делают что–то одно и только это. Так вот, если принять во внимание, что не бывает код без ошибок, а это вообще говоря так и есть, возникает проблема: как узнать правильно работает функция или нет. Разве легко это сделать, если назначение функции не совсем понятно? Чем больше у вас кода, правильную работу которого легко проверить, тем лучше.
Когда не следует выделять функцию
Дабы не впадать в крайности, просто необходимо написать также и эту главу. Правила и рекомендации — это замечательно, но никогда не стоит забывать, что главное для нас не методичное соблюдение всех правил, а чистый, понятный код.
Попробую привести несколько примеров, когда выделение функции, как правило, делает только хуже.
1. Процедура выбора из однотипной информации. Как правило, такая проблема возникает в конструкциях case (switch для C-подобныхьязыков) или «if … then … else if … then … else if … then …». Данные блоки могу включать в себя десятки, сотни и даже тысячи условий, занимая, разумеется, значительно больше одного экрана, но, разбивать такие блоки всё–таки не стоит.
Это справедливо для блоков с именно однотипными проверками. Если условия можно как–то сгруппировать, то можно каждую группу вынести в свою функцию (например, слова на букву «А» обрабатывает одна функция, на «Б» — другая и так далее).
2. Функции с действительно сложной логикой, как правило, также не удаётся красиво разбить на более мелкие составляющие. И проблема тут даже не в сложности как таковой, а в том, что не всему можно дать название. Бывает, что совершается настолько специфичное действие, что как не назови, всё равно понятно не будет.
3. Математические выражения также редко удаётся разбить на составляющие. Например, если вы что–то считаете по формуле — вам будет крайне сложно придумать адекватное название для расчёта части этой формулы.
4. Когда функция вам не мешает. То есть не следует проводить рефакторинг ради рефакторинга, маниакально выискивая «плохие» места в коде. Куда разумнее улучшать код только после того, как наткнулся на него в рамках какой–то задачи или более крупного рефакторинга.
Причин так говорить у меня сразу несколько. Во–первых, если вы раньше не натыкались на фрагмент кода, то может и в будущем никогда не наткнётесь, зачем же тратить на него время, если он нормально работает и пока никому не мешает? Во–вторых — не стоит забывать, что любой рефакторинг, как бы аккуратно он не проводился — это риск что–то испортить. Опять же, зачем рисковать просто так? Ну и наконец, всегда есть вероятность, что после рефакторинга вы через какое–то время поймёте, что логику надо поменять, а может и вовсе помножить на ноль эту ветку кода и что получится? Вы потратили время на улучшение уже устаревшего кода, то есть впустую, а могли, например, лишний раз протестировать функциональность или написать пару тестов.
Использование модулей
Удобно когда всё в одном файле и ничего искать не нужно? С одной стороны, конечно, да. Но это справедливо только для весьма небольших проектов.
Возможно повторюсь, но система каталогов — это по сей день самая удобная и понятная с система организации данных. Как я уже говорил — самое простое — организация данных по алфавиту. Нам это мало подойдёт. Не слишком удобно в случае программирования. Куда интереснее — тематический каталог.
Классические примеры — разнесение функций для работы с графикой (интерфейсом) и непосредственно логики программы. Часто выносят в отдельные модули функции для работы со строками, модули, отвечающие за сериализацию.
Стоит–ли говорить, что модулям следует давать мнемонические названия, чтобы было понятно, какие именно функции можно найти в том или ином файле.
Признаки необходимости выделения части модуля в отдельный модуль практически те же, что и в случае с функциями. Разумеется, с поправкой на то, что для модуля совершенно нормально быть больше по объёму и на ряд других очевидных моментов.
Более сложные способы организации данных
В программировании есть понятие — простые типы данных. Традиционно к ним относятся целые числа, числа с плавающей точкой, булевы (логические) типы данных, а также строки.
Множество переменных простых типов могут объединяться в массивы, но это ещё не всё. По–настоящему гибким программирование стало с появлением классов.
Это понятие, на мой взгляд, одно из самых трудных для понимания начинающих программистов, поэтому, я планирую уделить ему достаточно много времени, прежде чем перейти непосредственно к рефакторингу. Если вы знакомы с классами — вам может стать скучно, в таком случае — можете пропустить эту главу.
Я уже писал, что название переменной должно отражать её содержимое. То есть переменная с названием Line — должна каким–то образом описывать отрезок или прямую. Чаще всего данную сущность описывают двумя точками. Если пользоваться только простыми типами — нам придётся завести 4 переменные, например, X1, Y1, X2, Y2. А в случае, если у нас несколько линий, для того, чтобы отличать одну от другой — нам придётся переназвать переменные, например: Line1X1, Line1Y1, Line1X2, Line1Y2. Не очень удобно, правда?
Для решения подобных задач, уже давно, был придуман новый тип данных — record или struct, в зависимости от языка. Сейчас этот тип практически полностью вытеснен классами и его использование для большинства задач считается плохим тоном. Поэтому не буду останавливаться на этом, тем более, что рефакторинг к рекордам практически не применим.
И так, мы решили создать класс для нашей линии. Вот так видит декларация (описание) класса:
type
TLine = class(TObject)
public
X1: Integer;
Y1: Integer;
X2: Integer;
Y2: Integer;
end;
В данном случае — ключевое слово type — определяет начало блока объявлений новых типов данных. Стандартная конструкция языка Pascal. Для других языков синтаксис будет отличаться. TLine — название нашего класса. “= class(TObject)» — означает, что мы определяем класс, наследованный от класса TObject. Это базовый класс в Object Pascal. Все классы так или иначе наследованы от него. Подробнее на эту тему поговорим, когда будем обсуждать наследование.
Ключевое слово public определяет область видимости переменных и функций, объявленных в текущем блоке. Подробнее об этом также поговорим после. Ключевое слово end завершает определение класса.
Мы создали новый класс, но как им пользоваться? Для этого надо создать экземпляр класса:
var
Line: TLine;
begin
Line:= TLine. Create;
end;
В данном фрагменте кода мы объявляем переменную Line типа TLine, после чего, создаём новый экземпляр класса TLine и присваиваем его переменной Line.
Часто путают понятия класс и объект, так вот, в нашем случае объект — это Line, а класс — TLine.