Укрощая зверя: legacy-код, тесты и вы
Legacy-код — это «старый» код, возраст которого может быть как 2 месяца, так и 10 лет. Часто его писали разработчики, о которых в компании смутно помнят. Возможно, их вообще не было, а legacy-код родился вместе со Вселенной во время Большого Взрыва. С тех пор требования к нему менялись много раз, код правили в режиме «нужно было еще вчера», а документацию никто не писал, как и тесты. Legacy-код запутан и хрупок, в нем не видно ни начала, ни конца. Как к нему подступиться?
Здесь и далее кадры из сериала «Рик и Морти». Авторы Джастин Ройланд и Дэн Хармон.
Подбираться к нему нужно с тестов, но готовьтесь к боли. Проблемы начнутся уже с того момента, как вы решите взяться за такой проект. Вам нужно понять, зачем вы хотите за него браться, убедить руководство одобрить тестирование legacy-кода, а коллег — помочь. После этого возникнут вопросы, с чего начать изучение, какие тесты запустить первыми и как все не сломать? Но главное — как не впасть в отчаяние, когда поймете, что работе нет конца.
Кирилл Борисов 12 лет в индустрии, за эти годы прошел долгий путь по костылям, битому коду и гниющим каркасам старых систем: от монолитных учетных систем до микросервисов авторизации. Путешествие наградило его опытом и историями, которыми он поделится в виде ценных советов.
У меня есть мечта — когда-нибудь поработать над новым проектом. В нем все будет хорошо с самого начала и свежо, как первый снег: тесты, архитектура и смысл. Но это лишь мечта, потому что уже 10 лет я продаю свой талант за деньги и перехожу из одного legacy-проекта к другому.
За это время у меня не осталось нервов, но я могу сберечь ваши, поделившись своим опытом взаимодействия с legacy. Я расскажу, как укрощать зверя (legacy-код): работать с кодом и людьми, внедрять тестирование, нужно ли это делать и как к этому относятся и разработчики.
Чего здесь не будет:
- Советов, как писать тесты. Множество книг, статей и разных видео закрывают этот вопрос.
- Обсуждения методологий. BDD, TDD, ATDD — все на ваше усмотрение.
- Всего, что может нарушить NDA. У людей долгая память, а у юристов — длинные руки.
Что такое legacy-код
Определений много. Я считаю, что это »достаточно старый» код возрастом от 2 месяцев до 10 лет. Legacy-код запутан и хрупок, но как гигантский змей пожирает свой хвост.
Именно это не позволяет начать его спокойно тестировать. Все разработчики, от начинающих до опытных, когда приходят на legacy-проект, хватаются за копье тестов и несутся убивать это чудовище. Копье ломается, а вместе с ним люди. В итоге остается разработчик без признаков жизни, который работает на legacy-проекте десятки лет.
Возможно ли побороть этого зверя? Да, но нужна подготовка.
Подготовка
Борьба со зверем не так важна, как подготовительный этап. Он начинается с трех вопросов самому себе.
«Зачем я это делаю?» Серьёзно, зачем? Ведь варианта всего два.
- Вы хотите славы, чтобы люди говорили, как вы великолепны, потому что внедрили тесты.
- Чтобы облегчить жизнь себе, коллегам и компании.
Вы хотите работать ради комфорта других или собственной славы? Если для последнего, то с первым успехом интерес угаснет, вы угаснете, все угаснет. Вы перейдете на что-то другое, а гниющие останки ваших начинаний еще долго будут засорять проект.
«Знаю ли я, что делаю?» Если вы писали тесты, то знаете. Если нет, то прежде чем бросаться на монстра, овладейте азами: напишите 3–4 теста, покройте небольшую часть кода, набейте руку и почувствуйте силу.
«Есть ли у меня на это время?» Замечательнос благими порывами вмешиваться в код и улучшать его, работая на будущее. Но, возможно, на это нет времени, когда горит настоящее. Если так, то проекту нужны вы, а не светлый образ будущего.
Когда вы ответите на все вопросы утвердительно — переходите к следующему этапу.
Разведка местности
Изучите структуру проекта. У вас есть представление о структуре проекта, составных частях и принципе работы? Наверняка да, но, возможно, оно не совпадает с реальностью. Важно понимать, с чем придется столкнуться перед началом работы. Посвятите немного времени, чтобы пройтись по проекту и изучить его досконально.
Составьте схему зависимостей. Ни один проект не живет в вакууме. Базы данных, внешние сервисы, библиотеки — все это может использоваться в проекте.
Что сделано до вас? Возможно, вы не первый, кто боролся со зверем. Изучите наработки «предков», которые сгорели и ушли с проекта.
После разведки переходим к боевым действиям.
Борьба с организацией
Первый раунд — борьба с вашей организацией. Главный в ней — ваш менеджер, непосредственный начальник.
Менеджер. Он не так страшен, как кажется. Это обычный человек с обычными потребностями: сдать проект вовремя и без лишних проблем, получить за это деньги и бонусы и жить дальше.
Руководитель не против ваших начинаний. Он против того, чтобы вы кидались на проект с криками: «Тесты! Тесты! Тесты!». Если будете так делать, он посмотрит на вас как на человека, который тратит его время и тормозит остальных.
Покажите пользу. Менеджер говорит на языке пользы, времени и денег. Поймите, что им движет желание закрыть проект в срок и получить больше результата за меньшие ресурсы.
Тест не стоит подавать так:
— О, это будет круто!
Свои идеи надо продвигать так:
— В прошлом квартале у нас было 50 падений, которые можно было исправить на стадии разработки продукта. Исправить можно с помощью тестов. Они подтвердят, что внесённые изменения не изменили функционал, если мы того не ожидаем. Мы сэкономим часы, потраченные на устранение этих проблем и снизим сумму неустойки, которую выплатили из-за неработающей системы.
Произнося «оптимизация, деньги, экономия времени», вы говорите на языке менеджера. Когда он слышит эти слова, то проникается идеей. Он видит в вас не очередного оголтелого программиста, увлеченного свежей технологией, а человека, который заинтересован в улучшении продукта. Все ваши идеи он не одобрит сразу, но высока вероятность, что предложит сделать Proof Of Concept.
Proof of Concept повышает шансы. Предоставьте менеджеру отдельный изолированный участок кода, подсистему, которая покрывается тестами, запускается и работает. Это можно сделать, если взять один из наболевших багов, который всплывает с определенной периодичностью и попытаться его отловить и устранить тестом. PoF подтвердит ваши намерения, покажет, что у вас есть план и ваша работа приносит результат.
Не обещайте много. Для менеджера важны цифры: какие результаты, сроки и какими силами. Но менеджер — существо жадное до результатов. Не обещайте слишком много с самого начала. Если пообещаете решить все проблемы сразу, менеджер пойдет с этим к начальству. Начальство скажет: «Замечательно!», но сократит финансирование и срежет сроки в надежде, что мы сдадим систему намного раньше.
Когда договоримся с менеджером, переходим к тем, с кем приходится работать каждый день.
Коллеги
Не любят перемены. Типичный коллега на типичном legacy-проекте — это человек, который потерял веру в жизнь и будущее. Он не склонен к изменениям и смирился с судьбой: «Я здесь навсегда, выхода из болота нет». Проблема в том, что вы начинаете мутить воду в этом болоте. Вы требуете, чтобы он писал и запускал какие-то тесты, а он хочет выполнить свою работы, закрыть задачу и уйти домой.
Заинтересуйте коллег пользой — объясните, почему им станет лучше. Например, они постоянно тратят время и силы, оставаясь после работы, чтобы залечить какие-то баги. Надавите на это: «Если не деплоить на продакшн сломанный код, не придется тратить время на его починку. Напишем тесты, будем вылавливать такой код, меньше будет ломаться».
Проявите терпение и эмпатию. Вы общаетесь с людьми — спросите почему их беспокоит ваша идея? Предложите найти точку соприкосновения, чтобы понять друг друга. В этом основная тактика работы с людьми: не враждуйте, не сталкивайтесь лбами, будьте дружелюбнее.
Вам может помешать презентация идеи перед собранием коллег на очередном стендапе команды. В коллективе работает механизм «группового мышления»: никто не хочет принимать решение, все смотрят друг на друга и видят, что никто не горит энтузиазмом.
Для решения этой проблемы есть один грязный трюк. К сожалению, в своей жизни я пользовался им не раз.
Разделяйте и властвуйте. Подойдите к одному из коллег за обедом или в уголке и скажите: «Вся команда уже подписалась, ты один тормозишь процесс. Может быть, мы найдем общий язык?»
Пройдя всех по очереди вы подпишите всех. Всем будет стыдно признаться, что они подумали, что все остальные уже подписались. Это бесчестно и ужасно, но работает. Используйте этот прием ответственно и в крайнем случае. Помните — вам еще работать с этими людьми.
Когда разобрались с коллегами, нас ждет еще один жадный зверь.
Борьба с машиной
Это хитросплетение кода, которое называется продуктом. Начнем с азов.
Разберите хлам. Тестировать необходимо так, чтобы при минимальном воздействии на систему получать проверяемый результат. Но любая legacy-система полна данными: они добавлялись годами с момента запуска и влияют на поведение системы. Поэтому необходимо тестировать «с чистого листа».
Подготовьте «сферическую систему в вакууме»: опустошите источники данных, сделайте минимальные конфиги, которые запускает система, отключите все возможные «хаки» и «фичи». Заставьте систему запуститься. Если запустится — у вас есть минимальный набор данных, который необходим для функционирования. Это уже хорошая отправная точка — «чистый лист».
Применяя какие-то измеряемые воздействия, например, нажатие на определенную кнопку, вы получите измеряемый рабочий результат. С этим можно переходить к следующему шагу.
Распутайте данные. Любой legacy-проект работает на принципе «надо сдать вчера». Все, что вы проходили в университете или читали в книгах, здесь не работает. Когда начнете тестировать, столкнетесь, например, с циклической зависимостью, невозможной для воссоздания в программе, но необходимой для функционирования.
Начните с «главного объекта». Чтобы разобраться с лесом зависимостей, попробуйте задуматься о том, какой объект главный. Например, для системы учета склада главный объект — «ящик». С ним связан объект «полка», а с «полкой» — объект «ряд».
Воссоздайте необходимый минимум. Если смотреть по ссылкам между объектами и переходить всё глубже по дереву зависимостей, вы сможете определить необходимый минимум данных зависимых объектов. Вам его нужно воссоздать, чтобы система работала и могла функционировать для тестирования вашего функционала.
Не бойтесь менять ссылки. Возможно, придется засучить рукава и погрузиться глубоко в это месиво: удалять и менять ссылки, изменять структуру базы данных. Вы пришли, чтобы улучшить систему, поэтому не бойтесь вводить изменения.
Переходим к тестированию. Для запутанных старых продуктов хорошая стратегия — это smoke-тесты.
Smoke-тесты
Понятие «дымовое тестирование» пришло к нам из мира электроники. Один инженер собрал гигантскую схему с кучей лампочек и проводов. Но прежде, чем начал тестировать, просто включил схему в розетку. Если пошел дым, значит что-то пошло не так.
В информационных системах концепция smoke-тестов достаточно простая. Представим веб-сервис, у него есть endpoint. Попробуем отправить ему GET-запрос без параметров. Если по какой-то причине продукт неожиданно сломался (ошибка 500), то что-то пошло не так.
Smoke-тест — хорошее начало. Это тест, который проверяет некоторую функциональность и дает понять, что система работает или сломана. Даже простой запрос к самому простому endpoint уже затрагивает больше 1% кода. Такими небольшими тестами готовим плацдарм для дальнейшего тестирования.
Smoke-тест вскрывает множество проблем. Возможно, что за все время функционирования сервиса никто не догадался отправить запрос без параметров.
Используйте такую тактику, чтобы покрыть несколько основных точек входа в вашу программу: форму ввода логина/пароля, основные веб-сервисы, кнопки. Это что-то уже можно показать менеджеру и коллегам.
Функциональные тесты
Это не тесты отдельных классов или метод, а самый высокий возможный уровень тестирования определенной части функционала.
Представим функционал «сгенерировать отчет в сервисе». Вместо проверки отдельных частей, тестируем ситуацию запроса на создание отчета с определенными параметрами и получим файл с данными. Не обязательно знать механизм генерации отчета, но если с определенными входными данными сервис выдает определённые выходные данные, то этот черный ящик с некоторой вероятностью работает как надо.
Покрытие основного функционала подобными тестами позволяет быстро стартовать и сразу покрывает большие участки. Вы будете уверены, что код работает хотя бы приблизительно так, как вы представляете, приобретете больше уверенности, набьете руку и вскроете еще больше проблем.
Функциональные тесты — средство, а не цель.
На иглу функциональных тестов легко подсесть: «Я же тестирую реальный функционал! Это то, с чем сталкиваются пользователи».
Функциональный тест задействует большие куски кода, которые могут взаимодействовать с гигантскими объемами данных. Поэтому 3–4 функциональных теста — это хорошо, 10 хуже, а тысячи тестов, проходящие 9 часов, — перебор. К сожалению, такое тоже бывает.
После функциональных тестов беритесь за unit-тесты. Но о них я не буду рассказывать — вы и так все знаете.
Мы прошлись по азам машинного тестирования и возвращаемся к основной теме. Коллеги и менеджер — не самый страшный враг в бою с legacy. Самый страшный враг — вы сами.
Борьба с собой
Будьте готовы к тому, что путь будет казаться бесконечным. Работа на неделю в вашем плане займет полгода без перспектив завершения проекта.
Сопротивление неизбежно. Все союзники со временем начнут сомневаться, пытаться сойти с колеи, уговаривать бросить тесты и перейти к фичам. Будьте к этому готовы. Напомните всем, зачем вы вообще во все это ввязались, сколько сил и времени вложено. Слабый аргумент, но может сработать.
Никто не гарантирует успех. Даже если проявите героические усилия, вложите всего себя в работу, ваш проект все равно может сгореть, а крестовый поход с тестированием завершится ничем.
Это нормально, это не конец жизни и карьеры. Это даже не подтверждение того, что вы плохой профессионал. Единственный вывод здесь, что конкретно этот проект завершился неудачей.
Зато у вас появился опыт и знания. В следующий раз, когда возьмете в руку новое копье, и ваш конь разгонится на очередную ветряную мельницу, вы будете готовы сломать и это копье, но позже, другим методом и с меньшим ущербом.
Теперь обидное, горькое и вечное.
Напутствия
Не бойтесь обратной связи. Мне приходилось наступать в эту ловушку и видеть, как в нее попадают другие. Я что-то сделал и принес похвалиться коллегам: «Я сделяль!» Но неожиданно оказывается, что мой удобный механизм неудобен коллегам, а я и не спрашивал.
Пишите тесты, пробуйте то, что внедряете. Часто внедрение нового тестового фреймворка увлекает, а непосредственно сами тесты вы не пишите. Тогда может случиться так, что как только их напишите, поймете, что не сможете воспользоваться тестами. Возможно, коллеги тоже это видят, но молчат, либо просто не пишут тесты.
Помогайте коллегам с проблемами, даже если они об этом не просят. Помощь не означает взять всю работу на себя — это расслабляет коллег и снимает с них ответственность, а «автобусное» число снижается до единицы.Тогда вы становитесь человеком-тестировщиком: что-то сломалось, CI красный, тест-гайд. Помогайте в рамках разумного.
«Автобусное» число не шутка. Вы не сможете всегда тащить проект на себе. Каждый может выгореть, уйти в отпуск или уволиться. Поэтому передавайте коллегам ваши знания и ответственность, которая необходима, чтобы справиться без вас. Это поможет избежать неприятных звонков, когда вы расслабились на пляже, а CI снова красный.
Улучшайте механизмы тестирования. Многих проблем можно избежать просто потому, что медленные тесты неожиданно стали быстрыми. Раньше они занимали 20 строк кода, а теперь одну. Вы этого не замечали, потому что один раз что-то написали и забыли: «Работает — не трогай!» Но это правило не всегда применимо.
Вы — не центр Вселенной. Снова повторю, что «автобусное» число это не шутка. Не раз сталкивался с ситуацией, когда человек начал тестирование, а потом получил предложение в проект посвежее: все бросил, убежал, а комментариев и документации не оставил. Все работает до нового коммита, а починить невозможно — никто не понимает, как все устроено.
Не хочу, чтобы вы оказались этим человеком. Не превращайтесь в ограничивающий фактор.
- Пишите документацию.
- Проводите тренинги.
- Распространяйте свой опыт.
Когда все коллеги будут на одном уровне с вами (плюс-минус), процесс превратится из гонки одного человека в командную эстафету с передачей флага. Только через поддержку своих коллег вы добьетесь успеха. Если вы на проекте один, подумайте о том, что кто-то другой после вас также будет страдать в одиночестве. Дайте вашему последователю друга в виде документации, не позвольте умереть одному.
27 марта на Moscow Python Conf++ Кирилл расскажет о технической стороне рефакторинга кода с Python 2 на Python 3 — что может быть актуальнее в 2020 году.Что еще нас ждёт на конференции, можно посмотреть в статье с обзором программы или в соцсетях (fb, vk, twitter) и telegram-канале мероприятия. Скоро увидимся!