Виталий Корж, Team lead в Luxoft, опубликовал свою статью о рефакторинге на сайте DOU.UA. Представляем ее вашему вниманию.
Привет, меня зовут Виталий Корж, я JSON Developer в Luxoft Ukraine. Занимаюсь модификацией и адаптацией различных решений в основном на Java и JS. Для скриптовых целей использую Python и Ruby, так как CSS не позволяет форматировать JSON.
В процессе работы иногда приходится рефакторить свой код. Как правило, из-за изменений в требованиях продукта, программных и аппаратных обновлений, которые каскадом тянут за собой обновление других компонентов. Эта статья будет интересна разработчикам, которые хотят сделать обновление своих программ безболезненной и постоянной практикой.
Создание продукта никогда не заканчивается, но это отнюдь не означает, что программа не работает. Это значит, что текущий функционал удовлетворяет потребности пользователя на данный момент. Со временем качество и количество функций будет меняться, и в какой-то момент придется дорабатывать проект.
Приложения устаревают и засоряются, и для контроля за качеством существуют различные практики: писать простой и чистый код, покрывать тестами, делать ревью. Эта схема отлично работает до момента столкновения с бездной легаси устаревшим кодом. Причины могут быть совершенно разными: смена владельца фичи, устаревание стека, обновление библиотек, интеграция нового компонента…
Какое-то время сопротивляться деградации могут разве что идеально спроектированные и распределенные по модулям и сервисам проекты, где все компоненты низкосвязные и следуют всем правилам. Чем больше проект разрастается, тем сложнее следовать правилам и тем выше цена, которую придется заплатить. Как результат, в целях экономии хороший сервис пишется раз и навсегда.
Хорошие проекты сложно испортить, но стоит только принять несколько неверных решений, пойти на поводу обстоятельств — и перед нами предстанет совершенно иная картина. «Прелесть» плохих проектов кроется в их структурной и функциональной беспомощности. Они становятся идеальным практическим пособием для всех специалистов, где каждый может найти подтверждения своим сомнениям. Запутанные легаси-паутины из сервисов, протоколов и баз данных, в которых невозможно разобраться, тому пример.
Инициация
Чтобы написать проект с нуля, нужно приложить немало усилий, но это позволяет сохранить импульс на годы вперед. Внесение правок в изначальный прототип не должно быть чем-то критичным, в то же время потребность редизайна кластера на старте является громким звоночком о том, что «ракета» не взлетит. Это тот случай, когда стоит взять паузу и начать сначала. Поскольку необходимость вносить изменения может быть продиктована глобальными трендами, такими как использование нового провайдера услуг (авторизации, обработки, хранения).
В свою очередь, переписывание проекта ставит перед командой разработки немного другие задачи. Необходимо поддерживать определенный баланс, определяемый договоренностями, между поддержкой существующего функционала и инвестициями в новую разработку. Менеджменту и командам иногда сложно отыскать правильный баланс в развитии продукта, что может негативно повлиять на него в целом.
Готовые проекты уже имеют весь функционал и требования, если что-то непонятно, всегда можно подсмотреть. Технические требования к продукту также выставлены. Команды собраны. Выбирается микросервисный подход как самый комфортный для организации процесса. Описывается архитектура будущего приложения. Между командами распределяются задачи, формируется список на начальный этап. Так начинается рефакторинг.
А что если?
Есть сценарии, которых следовало бы избегать, но они периодически встречаются в проектах. Они продиктованы бизнесом или возможностями команды, но в то же время могут нести вред всему процессу.
Клиент настаивает на поддержке существующего проекта и одновременно на его переработке. Это позволит команде ознакомиться с продуктом получше и понять его специфику.
Вариант оптимальный для бизнеса, но его не всегда следует исполнять в точности, как сказано. Опыт свидетельствует, что желательно отделить команду поддержки от команды разработки. Полезной экспертизы получить не удастся, а подхватить ненужных идей и замылить взгляд вполне. Замечено, что специалисты, знающие, где подсмотреть реализацию, часто копируют ее без особого анализа используемого кода. И чем сложнее реализация, тем чаще к этому прибегают.
Каждая из команд делает абсолютно независимый компонент и должна работать с другими командами, используя список задач. Неважно, как поделены команды, горизонтально или вертикально, если работа ведется над общими компонентами, следует выработать общие подходы и вести совместное планирование. Это нужно для синхронизации процесса разработки и оперативного решения возникающих проблем, к которым можно отнести различную трактовку требований и ограничения реализаций.
Не стоит отказываться от текущей базы данных. Нужно создать абстрактный слой представления поверх существующей модели данных, который позволит работать с любой базой в будущем.
Попытка встроить старую базу в новую архитектуру с высокой долей вероятности закончится провалом, стоит изучить возможность бесшовной миграции: спроектировать новый DataSource и наладить ETL-процессы. Всех проблем это не решит, но значительно упростит работу с новым приложением и развяжет руки в принятии решений.
У нас есть POC, почему бы не переименовать его в MVP
Никто не запрещает создавать тестовые приложения высокого качества, но стоит помнить, что задача концепта не решать бизнес-задачу, а быть полигоном для оттачивания идей (не умений). Концепт может быть построен на неподготовленных для используемого стека решениях. В то же время рабочий прототип уже должен иметь все необходимые для сервиса составляющие.
Стоит максимально размыть границы между технологиями, это позволит добиться универсальности и взаимозаменяемости исполнителей.
Идеальный случай, когда специалист свободно владеет различными языками и не мыслит стереотипами одного языка. Но это встречается не так часто. И надеяться, что код будет одинаково восприниматься специалистами с разным бэкграундом, также не стоит. Желательно выбрать один технологический стек для каждой из команд (не актуально, если у вас сотня-другая человек) и внести жесткое разделение ответственности, конечно, если позволяют ресурсы. В противном случае придется иметь дело с технологическим зоопарком, где не у каждого зверя есть свой смотритель.
Если все команды пишут интеграционные тесты, почему бы не выделить их в отдельный проект, в который будут писать все команды. Идея мастер-репозитория возможна, но сам подход создает сервис, за который в результате никто не отвечает. А этого нельзя допускать. Если есть сервис, у него должен быть владелец. А чем важнее сервис, тем выше спрос. Если сервис никому не нужен, стоит задуматься над целесообразностью его наличия.
Пора сыграть в микросервисное бинго
Чем проще архитектура, тем легче будет держать сервис в изначальных пределах. Усложнить всегда будет время. Единственные карточки, которые должны быть заполнены, — мониторинг и общесервисные шаблоны. Если сервисы похожи друг на друга, их легче развивать. Добавление нового подхода должно быть аргументировано. Простой пример — вариант декомпозиции по домену или ответственности, когда один и тот же сервис может иметь совершенно разную структуру в зависимости от выбранного подхода.
Достать ножи
Со временем по сервисам (обновления библиотек, требования безопасности, эксплуатационные издержки) накапливается технический долг, с которым следует планомерно разбираться. Не всегда возможно устранить все проблемы, но всегда стоит минимизировать их.
Рефакторинг — это не только производительные сервисы, но и эффективная архитектура. Вся система должна соответствовать меняющимся требованиям. Условия эксплуатации никогда не остаются статичными, при появлении новых компонентов нагрузка на них и систему в целом возрастает. Поэтому нужно периодически пересматривать архитектуру в поисках новых возможностей для оптимизации. При этом всегда обращайте внимание на уместность: например, перевод части сервисов на serverless может сильно перекроить существующую архитектуру и потребовать больших инвестиций, что в итоге может и не окупиться.
После всех изменений рекомендуется рассмотреть механизмы того, как и когда стоит действовать. Возможность ставить под сомнение целесообразность выбранной стратегии должна быть неотъемлемой частью ревью, а все ответственные участники должны договориться и придерживаться его.
Со временем системы также сталкиваются с технологическим устареванием, превращая техдолг из сервисной проблемы в архитектурную. Так, начав проект на одном фреймворке, через год можно обнаружить различные его версии на проде. Выход новой версии фреймворка или обновленной версии языка требуют пересмотра всех сервисов.
Постоянные обновления могут смягчить переход на новую версию используемого инструмента. Шаблонная основа для сервисов предоставит необходимый контроль над изменениями. Большинство таких обновлений можно автоматизировать, а наличие автотестов должно максимально упростить этот процесс.
Хорошие вдохновители постоянной оптимизации — облачные платформы, которые с завидной регулярностью предлагают новые возможности по интеграции и уменьшению затрат на содержание. То, что зачастую служит мотивом для пересмотра инфраструктуры, с таким же успехом может помочь с пересмотром приложения.
Вывод
Независимо от домена и выбранных технологий, ваш код рано или поздно устареет. Поэтому не стоит бороться с причинами, вместо этого уделите внимание тому, как код можно развивать и перекраивать, подстраиваясь под новые нужды. Чем динамичнее используемые технологии, тем меньшую роль играет изящность решений, а освободившееся время лучше потратить на простые и понятные тесты.
Пользователь — величайшая сила во вселенной, по прихоти которой та вращается, пользователь диктует требования и определяет тренды.
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]