Перевод статьи Адриана Б.Г. «Constantly improving the codebase using a 3 step technique (with example)».
Как и в случае с поддержанием порядка в доме, не надо ждать весны, чтобы избавиться от ужасов прошлого.
Контекст
Я начал применять эту технику, работая над legacy-проектом, который развивался на протяжении многих лет. Необходимость работать над улучшением кодовой базы, одновременно занимаясь доставкой нового функционала, пришла как бы сама собой.
У нас практически никогда не было времени на рефакторинг целого модуля. Времени едва хватало, чтобы запатчить его и отсрочить решение проблемы. У нас были классы, соответствующие всем антипаттернам сразу. План сделать код более поддерживаемым остановил бы продакшен как минимум на несколько спринтов. Это был не вариант.
Единственное, что нам оставалось, это потихоньку фиксить проблемы между текущими задачами. Вот несколько наших достижений за год:
- разделение 4000 строк кода божественных классов;
- выделили функционал в другие подпроекты в качестве сервисов;
- множество мелких улучшений.
Это немного, но мы постоянно были в спешке, делали ежедневные релизы и проводили множество А/В тестов. Каждое маленькое улучшение уменьшало количество времени, необходимого для следующей задачи (если она касалась того же отрывка кода).
Непрерывный рефакторинг
Ежедневная работа над улучшением состояния вашего кода это хороший подход. Скорее всего вам не нужно (и/или вы не хотите) переписывать ваше приложение полностью, необходимо лишь продолжать улучшать его.
Таким образом проект будет «исцелять себя самостоятельно». Работающие части продукта, не нуждающиеся в изменениях, останутся в первоначальном виде. А тем частям, которые часто меняются, постоянные улучшения пойдут только на пользу.
«Что еще нужно осознать в отношении рефакторинга, это что он занимает время. Все работы, связанные с рефакторингом, от части 6 до 11, могут быть проделаны максимум за час. Над значительными переработками кода в работающей системе мы трудились месяцами и даже годами. Когда у вас есть система, которая уже в продакшене, и вы должны постоянно добавлять в нее функционал, вам будет непросто убедить менеджеров, что нужно приостановить прогресс на пару месяцев, пока вы все почистите. Вместо этого нужно поступать, как Гензель и Гретель (дети, персонажи сказки братьев Гримм, обгрызавшие с краев пряничный домик ведьмы, – прим. перев.): «откусывать» по чуть-чуть по краям, немножко сегодня, чуть больше завтра». («Рефакторинг» Кента Бека и Мартина Фаулера)
Вы удивитесь, узнав, как много общего у рефакторинга и уборки, включая 3 шага, которые я адаптировал и изложил в этой статье.
Для начала нужно выбросить столько, сколько возможно, чтобы освободить место. Затем с помощью небольших приемчиков нужно очистить это пространство. После этого можно покупать новые вещи, если нужно.
Оставьте код в лучшем состоянии, чем он был, когда вы нашли его!
Каждый раз при изменении куска кода у вас возникает возможность или создать, или погасить технический долг. Добавляя новый функционал или исправляя баги, я иду таким путем:
- Удаление неиспользуемого кода (чистка мертвого кода). Сюда относится (в числе прочего) неиспользуемые фичи, комментарии, забытые предложения, которые использовались в прошлых сессиях отладки и прочие «мелочи».
- Рефакторинг кода. Это действие преследует две главные цели – улучшение поддерживаемости и расширяемости кода. Есть множество техник рефакторинга (включая смену имен переменных). Занимаясь этим, вы глубже проникаете в суть вещей и начинаете лучше понимать, как все работает и как вы можете что-то расширить/исправить.
- Выполнение плановой задачи. Теперь у вас более чистый код, и решить текущую задачу будет проще.
Вам НЕ нужно ждать, пока менеджер остановит продакшен и назначит целый спринт для погашения технического долга. Исправляя проблемы маленькими кусочками, вы создаете непрерывный процесс улучшения, а это ситуация win-win-win.
Предостережения
Рефакторинг без автоматических тестов и/или хорошей QA-команды, которая будет вас прикрывать, это экстремальный спорт, поверьте мне (плавали, знаем).
Прежде чем сделать предварительную оценку времени на выполнение задачи, нужно просмотреть код и принять во внимание новые факторы: время на чистку и добавление модульных тестов при необходимости. А теперь умножьте результат на 3 или на 6, и вы получите приблизительно верное время.
Практический пример
Приведенный пример написан на JavaScript, но в нем нет никаких специфических для языка инструкций.
У вас есть модуль списка TODO (legacy-код), который выполняет два действия: перечисляет все TODO или выбирает только старые (не сделанные). На ваш стол попадает новый таск: «добавить новый функционал: юзер должен иметь возможность делать список задач на следующие годы».
Чтобы максимизировать эффект применяемой техники, давайте предположим, что со времени написания этого модуля вы перешли на JS6 и начали использовать Lodash (underscore). Вы заметите, что это очень улучшит качество кода.
Первый отрывок это оригинальный код. Второй – тот же код после применения первых двух шагов из нашего плана действий: «1. Удаление мертвого кода» и «2. Рефакторинг». После выноса общего функционала для избежания дублирования кода будет очень просто добавить новую функцию (3-й отрывок).
1-й
[javascript]var currentYear = 2017
//private function, fetch from DB
function getTodos() {
return [
{ name: "boogy", checked: true, year: 2014 },
{ name: "alfa", checked: false, year: 2017 },
{ name: "back to the future", checked: false, year: 2021 },
]
}
//we need a new functionality, similar with this one
//but instead of OLD entries we need only the future ones
function PublicGetOldTodos() {
var todosOrig = getTodos()
//stupid comment like "this filters the todos"
var todos = new Array();
var length = todosOrig.length;
//"this is a loop" comment
for(var i = 0; i < length; i++)
{
if(todosOrig[i].year < currentYear && todosOrig[i].checked == false)
{ todos.push(todosOrig[i]);
}
}
//other comments if (todos.length == 0) { console.log("a dev forgot some code here")
}
if (isIE6) {
//god help us all
}
//don’t overflow if (todos.length > 10){
//10 is a magic number, we hate magic numbers
return todos.slice(0, 10)
}
return todos
}[/javascript]
2-й
[javascript]//we removed the not used code, added parameters and extract the common functionality
//with the feature we need to implement
function getFilteredTodos(limit = 20, predicate = () => true) {
var todos = getTodos()
todos = _.filter(todos, predicate)
todos = _.slice(todos, 0, limit)
return todos
}
function PublicGetOldTodos(limit = 10) {
var condition = (value, index, collection) => {
return value.year < currentYear && value.checked == false
}
return getFilteredTodos(limit, condition)
}[/javascript]
3-й
[javascript]function PublicGetFutureTodos(limit = 5) {
var condition = (value, index, collection) => {
return value.year > currentYear && value.checked == false
}
return getFilteredTodos(limit, condition)
}[/javascript]
Лучший сценарий
Для проекта (и для вас тоже) было бы хорошо иметь покрытие модульными тестами. Прежде, чем вы прикоснетесь к коду, было бы прекрасно проверить, добавить и создать дополнительные модульные тесты. Это позволит вам быть беспощадным, проходя первые два пункта плана (чистку и рефакторинг).
В нашем примере нужно написать серию тестов, чтобы убедиться в том, что legacy-код работает. Скорее всего вы при этом найдете новые баги, с которыми нужно будет разобраться на стадии рефакторинга.
Добавляя новый функционал, нужно писать новые тесты, чтобы проверить свою работу и лучше спать ночью.
Рекомендация
Это хорошая возможность изучить новые техники рефакторинга. Помните, что работать нужно малыми итерациями, и тогда вскоре вы заметите большие улучшения по всему проекту.
«Чтобы изменить свои привычки в плане уборки, людям сначала нужно изменить свой образ мышления», – Мари Кондо.
TL;TR
0. Найдите код, который нужно изменить.
1. Удалите мертвый код.
2. Проведите рефакторинг кода.
3. Выполните вашу задачу.
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]