- PVSM.RU - https://www.pvsm.ru -
Был поздний вечер.
Мой коллега только что записал в репозиторий код, над которым работал целую неделю. Мы делали тогда графический редактор, а в свежем коде были реализованы возможности по изменению фигур, находящихся в рабочем поле. Фигуры, например — прямоугольники и овалы, можно было модифицировать, перемещая небольшие маркеры, расположенные на их краях.
Код работал.
Но в нём было много повторяющихся однотипных конструкций. Каждая фигура (вроде того же прямоугольника или овала) обладала различным набором маркеров. Перемещение этих маркеров в разных направлениях по-разному влияло на позицию и размер фигуры. А если пользователь, двигая маркеры, удерживал нажатой клавишу Shift, нам, кроме того, надо было сохранять пропорции фигуры при изменении её размера. В общем — в коде было много вычислений.
Код, о котором идёт речь, выглядел примерно так:
let Rectangle = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
};
let Oval = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeTop(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeBottom(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
};
let Header = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
}
let TextBlock = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 10 однотипных строк вычислений
},
};
Меня все эти вычисления, все эти почти одинаковые строки, сильно зацепили.
Код не был чистым.
Основной объём однотипных строк наблюдался в тех местах, где задавалось перемещение фигур в одном и том же направлении. Например, в методе Oval.resizeLeft()
был код, похожий на тот, который можно было найти в Header.resizeLeft()
. Дело было в том, что оба эти метода отвечают за изменения фигуры, выполняемые при перемещении маркера влево.
Похожими были и методы фигур, имеющих одну и ту же форму. Например, Oval.resizeLeft()
был похож на все остальные методы фигуры Oval
. Все эти методы работали с овалами — отсюда и их сходство. Дублирующийся код можно было найти в объектах Rectangle
, Header
и TextBlock
, так как текстовые блоки представляли собой прямоугольники.
У меня появилась идея.
Можно избавиться от дублирующихся конструкций, по-другому сгруппировав код. Например — так:
let Directions = {
top(...) {
// 5 уникальных строк вычислений
},
left(...) {
// 5 уникальных строк вычислений
},
bottom(...) {
// 5 уникальных строк вычислений
},
right(...) {
// 5 уникальных строк вычислений
},
};
let Shapes = {
Oval(...) {
// 5 уникальных строк вычислений
},
Rectangle(...) {
// 5 уникальных строк вычислений
},
}
Затем можно прибегнуть к композиции и собрать из этих базовых методов то, что нужно:
let {top, bottom, left, right} = Directions;
function createHandle(directions) {
// 20 строк кода
}
let fourCorners = [
createHandle([top, left]),
createHandle([top, right]),
createHandle([bottom, left]),
createHandle([bottom, right]),
];
let fourSides = [
createHandle([top]),
createHandle([left]),
createHandle([right]),
createHandle([bottom]),
];
let twoSides = [
createHandle([left]),
createHandle([right]),
];
function createBox(shape, handles) {
// 20 строк кода
}
let Rectangle = createBox(Shapes.Rectangle, fourCorners);
let Oval = createBox(Shapes.Oval, fourSides);
let Header = createBox(Shapes.Rectangle, twoSides);
let TextBox = createBox(Shapes.Rectangle, fourCorners);
То, что у меня получилось, было в два раза меньше того, что написал коллега. В моём варианте программы полностью отсутствовали повторяющиеся фрагменты! Чистейший код. Если нужно было изменить поведение системы, относящееся к конкретному направлению, или к конкретной фигуре, можно было сделать это в одном месте, а не переписывать несколько методов.
Была уже поздняя ночь (я увлёкся). Я влил результаты рефакторинга в ветку master
и отправился спать, гордый тем, как я «причесал» неопрятный код коллеги.
…прошло не так, как ожидалось.
Руководитель пригласил меня на разговор за закрытыми дверями, в ходе которого вежливо попросил меня откатить мои изменения. Меня это потрясло. Ведь старый код — это же сплошной бардак. А мой код был чистым.
Я нехотя исполнил просьбу, но мне понадобились годы для того, чтобы понять правоту руководителя и коллеги.
Зацикленность на «чистом коде» и стремление к удалению дубликатов — это одна из ступеней развития программиста, через которую проходили многие из нас. Когда мы не чувствуем уверенности в своём коде, возникает желание привязать наше восприятие самооценки и профессиональной гордости к чему-то такому, что можно измерить. Набор строгих правил линтинга, схема именования сущностей, структура файлов проекта, минимизация дублирующегося кода.
Нельзя автоматизировать борьбу с повторяющимся кодом, но эта борьба, совершенно точно, становится легче с практикой. Обычно после каждого изменения можно сказать о том, больше или меньше одинаковых конструкций стало в проекте. В результате избавление от дублирующихся фрагментов программы воспринимается как улучшение некоего объективного показателя качества кода. И, что хуже, это искажает самосознание программиста: «Я из тех, кто пишет чистый код». Это так же сильно влияет на человека, как и любой другой род самообмана.
Как только программист узнаёт о том, как создавать абстракции [2], он вполне может этим увлечься. Он, видя дублирующийся код, будет находить абстракции там, где их нет. А после нескольких лет такой практики дублирующийся код будет обнаруживаться абсолютно везде. Абстрагирование станет новым талантом программиста. Если кто-то скажет ему, что абстрагирование — это добродетель, он это примет. И он начнёт осуждать тех, кто не преклоняется перед «чистотой».
Теперь я понимаю, что у моего «рефакторинга», и у того, как я к нему подошёл, было два серьёзных недостатка:
Говорю ли я о том, что вам нужно писать «грязный» код? Нет. Я предлагаю лишь хорошо подумать над тем, что имеют в виду под понятиями «чистый» и «грязный». Что-то приводит вас в негодование? Вы чувствуете в чём-то правильность, красоту, изящество? Уверены ли вы в том, что можете применять подобные понятия, описывая конкретные результаты работы программистов? Каким образом эти понятия влияют на то, как пишут и модифицируют [3] код?
Я, конечно, о таких вещах тогда не думал. Я много размышлял о том, как выглядел код — но не о том, как он развивался вместе с командой, состоящей из людей, которым не чуждо ничто человеческое.
Программирование — это путь. Подумайте о том, как далеко вы продвинулись от написанной вами первой строчки кода, до того места, где находитесь сейчас. Я полагаю, что вы, в первый раз извлекая функцию или подвергая класс рефакторингу, с восхищением наблюдали за тем, как эти приёмы упрощают сложный код. Если вы чувствуете гордость за своё дело, то у вас появится стремление к преследованию чистоты кода. Поддайтесь на некоторое время этому стремлению.
Но не останавливайтесь на этом. Не становитесь фанатичным приверженцем чистого кода. Чистый код — это не цель. Это — попытка как-то осмыслить огромную сложность систем, с которыми мы имеем дело. Это защитный механизм, который программист применяет тогда, кода ещё не уверен в том, как некое изменение повлияет на кодовую базу проекта, но ищет ориентиры в море неизвестности.
Пусть идея чистого кода станет вашим ориентиром. А потом — отпустите эту идею.
Уважаемые читатели! Как вы относитесь к «чистому коду»?
Автор: ru_vds
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/343961
Ссылки в тексте:
[1] Image: https://habr.com/ru/company/ruvds/blog/484610/
[2] абстракции: https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction
[3] модифицируют: https://overreacted.io/optimized-for-change/
[4] Источник: https://habr.com/ru/post/484610/?utm_source=habrahabr&utm_medium=rss&utm_campaign=484610
Нажмите здесь для печати.