Прощай, чистый код
- Posted by swiki
- Posted on 29 марта, 2020
- Рефакторинг кода
- No Comments.
Это был поздний вечер.
Мой коллега только что проверил код, который они писали всю неделю. Мы работали над графическим редактором, и они реализовали возможность изменять размеры фигур, таких как прямоугольники и овалы, перетаскивая маленькие значки по их краям.
Код работал.
Но этот код изобиловал дублями. Каждая фигура (например, прямоугольник или овал) имела свой набор значков, и перетаскивание каждого из них в разных направлениях по-разному влияло на положение и размер фигуры. Если пользователь удерживал Shift, нам также нужно было бы сохранить пропорции при изменении размера. В коде была куча математики.
Код выглядел примерно так:
let Rectangle = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
};
let Oval = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeTop(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottom(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
};
let Header = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
}
let TextBlock = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
};
Этот повторяющийся код с одинаковой математикой действительно беспокоил меня.
Этот код не был чистым!
Большая часть повторений была между аналогичными направлениями. Например, Oval.resizeLeft()
имели сходство с Header.resizeLeft()
. Так было потому, что они оба имели дело с перетаскиванием значка слева.
Другое сходство было между методами для той же формы. Например, Oval.resizeLeft()
имели сходство с другими методами Oval. Это было потому, что все они имели дело с овалами. Было также некоторое дублирование между Rectangle
, Header
и TextBlock
, потому что текстовые блоки были прямоугольниками.
У меня родилась идея.
Мы могли бы удалить все дублирование , сгруппировав код следующим образом:
let Directions = {
top(...) {
// 5 unique lines of math
},
left(...) {
// 5 unique lines of math
},
bottom(...) {
// 5 unique lines of math
},
right(...) {
// 5 unique lines of math
},
};
let Shapes = {
Oval(...) {
// 5 unique lines of math
},
Rectangle(...) {
// 5 unique lines of math
},
}
и затем мы смогли бы использовать композицию для поведения разных элементов:
let {top, bottom, left, right} = Directions;
function createHandle(directions) {
// 20 lines of code
}
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 lines of code
}
let Rectangle = createBox(Shapes.Rectangle, fourCorners);
let Oval = createBox(Shapes.Oval, fourSides);
let Header = createBox(Shapes.Rectangle, twoSides);
let TextBox = createBox(Shapes.Rectangle, fourCorners);
Результирующий код стал в половину меньше, а дублирование полностью исчезло! Это чистый код. Если мы захотели бы изменить поведение для определенного направления или формы, мы могли бы сделать это в одном месте вместо обновления методов в каждом типе элементов.
Была уже поздняя ночь (я увлекся). Я слил результаты моего рефакторинга в мастер-ветку репозитория и пошел спать, гордый тем, как я распутал грязный код моего коллеги.
Следующее утро
… все пошло не так как ожидалось.
Мой босс пригласил меня на беседу тет-а-тет, где он вежливо попросили меня отменить мои ночные изменения. Я был в ужасе и непонимании. Старый код был символом беспорядка, а мой был чист!
Я неохотно согласился и мне потребовались годы на то, чтобы понять, что он был прав.
Это этап
Мания «чистого кода» и устранения дублей — это этап, который многие из нас проходят. Когда мы не чувствуем себя уверенно в нашем коде, возникает соблазн пойти на поводу у нашего чувства собственной значимости и профессиональной гордости и приложить его к чему-то, что можно измерить. Набор строгих правил lint, схема именования, структура файла, отсутствие дублирования.
Вы не можете сразу автоматически удалять дубли, но это действительно становится легче с практикой. Обычно вы можете сказать стало ли их больше или меньше после каждого изменения. В результате, удаление дублирования похоже на улучшение некоторой объективной метрики в коде. Хуже того, это смешивается с чувством самооценки людей: «Я тот человек, который пишет чистый код» . Это чувство так же сильно, как любой вид самообмана.
Как только мы научимся создавать абстракции, будет соблазнительно развить эту способность и высасывать абстракции из пальца, когда мы увидим повторяющийся код. После нескольких лет программирования мы видим повсюду повторение. И абстракция — наша новая серебряная пуля. Если кто-то скажет нам, что абстракция — это добродетель, мы проглотим это. И мы начнем судить других людей за то, что они не поклоняются «чистоте кода».
Теперь я вижу, что мой «рефакторинг» был катастрофой в двух отношениях:
- Во-первых, я не поговорил с человеком, который написал это. Я переписал код и заменил его без предупреждения и обсуждения. Даже если это было улучшение (во что я больше не верю), это ужасный путь. Здоровая команда программистов постоянно старается укреплять доверие. Переписывание кода вашего товарища по команде без обсуждения с ним является огромным ударом по мнению о вашей способности эффективно сотрудничать совместно с командой.
- Во-вторых, ничто не проходит бесследно. Мой код заменял возможность изменяться под требования на видимое уменьшение дублирования, и это была не очень хорошая сделка. Например, позже нам понадобилось много особых случаев и поведений для разных значков разных форм. Моя абстракция должна была бы стать в несколько раз более сложной, чтобы позволить себе реализовать эти изменения. Тогда как с оригинальной «грязной» версией такие изменения произвести было легче легкого.
Думаете я говорю, что вы должны писать «грязный» код? Нет. Я предлагаю глубоко задуматься о том, что вы имеете в виду, когда говорите «чистый» или «грязный», прежде чем проводить рефакторинг. Вы чувствуете бунт? Праведность? Красота? Элегантность? Насколько вы уверены, что можете назвать конкретные инженерные результаты, соответствующие этим качествам? Как именно они влияют на способ написания и последующего изменения кода в соответствии с меняющимися требованиями?
Я точно не задумывался о какой-либо из этих вещей. Я много думал о том, как выглядит код, а не о том, как он появился и как будет развиваться в дальнейшем людьми из моей команды.
Программирование — это длинная дорога. Подумайте, как далеко вы продвинулись от момента написания своей первой строки кода до того места, где вы сейчас находитесь. Я считаю, что впервые было радостно видеть, как извлечение функции или рефакторинг класса могут сделать сложный код простым. Если вы находите гордость в своем ремесле, заманчиво стремиться к чистоте кода. Уделяйте этому некоторое время.
Но не зацикливайтесь на этом. Не будь чистым фанатиком кода. Чистый код не цель. Это попытка привнести некоторый смысл в огромную сложность систем, с которыми мы имеем дело. Это защитный механизм, когда вы еще не уверены, как изменения повлияют на кодовую базу, но вам нужен маяк в море неизведанного.
Пусть чистый код направляет вас. А затем пусть он идет своим путем.
Перевод из блога Дэна Абрамова. Источник.