Как стать автором
Обновить
35.25

Рефакторинг в BI-проектах: когда и зачем переписывать «рабочий» код

Время на прочтение11 мин
Количество просмотров1K

Команда авторов ГК Luxms, вендора платформы бизнес-аналитики Luxms BI:

  • Александр Тютюнник — директор по развитию бизнеса

  • Дмитрий Дорофеев — главный конструктор

  • Илья Гурешидзе @IlyaGureshidze — тимлид фронтенд-команды

  • Константин Буров — тимлид команды аналитиков

В крупных федеральных организациях всё активнее используется подход управления на основе данных, который требует активного использования и постоянной переделки, развития, модификации аналитических приложений, отчётов, данных. Тот опыт и наши наработки, которыми мы хотим поделиться в рамках данной статьи, приносят пользу на многих проектах, где речь идёт о сотнях аналитических отчётов и дэшбордов, нескольких тысячах показателей и сотнях и тысячах активных пользователей, где, самое главное, вендорские решения кастомизируются внутренними командами заказчика. Для таких случаев всё, о чём мы расскажем дальше, очень важно, для остальных — надеемся, что будут полезны отдельные мысли и технические решения.

Начнём с простого и наболевшего. Когда создаётся первая версия дэшборда, задача звучит просто: «показать данные хоть как-нибудь и побыстрее». Не до архитектуры, не до производительности — главное, чтобы цифры появились, и руководство смогло принять правильное управленческое решение. Потом уточняется задача, добавляются новые требования, меняются источники, добавляются разрезы данных, растёт нагрузка. И вот тот самый дэшборд, собранный на скорую руку, оказывается в проде — и технически работает не так и не с той скоростью, как нужно. А далее необходимо развивать функционал, обновлять версию. И сложности растут.

В статье расскажем, почему так происходит и почему «оптимизация» — это не про критику, а про работу с реальностью, со сложной реальностью мира IT и мира данных. А еще — почему важно не только чинить, но и уважать чужой код. 

Оптимизация BI-приложений — это не магия, а просто другой этап сложного IT-проекта в области данных. Большинство «кривых» решений появляются из-за вполне понятных ограничений. Делать сразу идеально — невозможно.

Подвиги разработки: когда скорость важнее архитектуры

Представьте, в понедельник — срочный показ для руководства. Команда на ушах, все бросаются что-то делать, естественно, переработки, экстренные запросы вендору. Главное — чтобы к моменту демонстрации всё работало. В таком режиме архитектура и долгосрочные последствия отходят на второй план, если хоть какой-то подход срабатывает — его используют. Здесь не до качественного тестирования и работы «как в книжках описано» — дедлайны горят.

Такой код создается «на один раз». Никто не думает о том, что будет через месяц, когда он попадёт в прод и начнёт взаимодействовать с другим кодом, который ещё может даже не написан. После успешного показа — эйфория. Команда молодцы, цель достигнута. Подвиг совершён! Мало кто хочет заниматься рефакторингом, ведь всё работает. Через месяц — новый показ, новый подвиг. Очередные костыли, и снова команда — герои. Но когда таких подвигов становится слишком много, они начинают пересекаться. Один костыль ломает другой, вендор выкатывает новую версию платформы, и вдруг — подвиг невозможен. Почему всё работало, а теперь нет? Они же делают всё «как раньше»!

Мы часто сталкиваемся с тем, что у клиента что-то «тормозит» или работает не так, как задумано. Идём разбираться — а там сложные селекты, тяжёлые джойны, неверная логика. Всё сделано, мягко говоря, неоптимально. Мы оптимизируем, ускоряем, клиент благодарен. Думаете, дело в том, что кто-то не знает, как правильно делать? Нет.

Почти все проекты с данными проходят через это. Даже если работают опытные разработчики. Даже если проект изначально делался «по уму». Тут важно понимать, «оптимизаторы» пришли в момент, когда можно и нужно было всё улучшить. А команда, которая делала проект, работала в условиях дедлайнов и иногда полной неопределённости:

  • Нет нормального ТЗ. Или есть, но на половину состоит из готовых таблиц в базе — “и так понятно”;

  • Источники данных плавают. Сегодня Excel, завтра в PostgreSQL, послезавтра что-то ещё;

  • Бизнес ещё сам не понимает, какие метрики нужны. Через неделю всё поменяется;

  • Команда в стрессе. Жёсткие дедлайны. Главное — чтобы работало. О каком ревью речь?

  • И, конечно, вопрос производительности вообще не стоит. Он появится позже.

В этих условиях возникает «компромиссный» код. Да, он неидеален. Да, он тяжёлый, монолитный, местами страшненький. Но он работает. И он появился за два дня, а не за две недели. Вывод: подвигов не избежать, но их последствия можно минимизировать. Главное — вовремя вернуться и навести порядок.

Два аспекта — психологический и технический, — а решение одно

Психологический —  команда, которая не раз добивалась успеха, вдруг терпит неудачу, хотя делает всё «как обычно». Это вызывает раздражение и недоумение. Ведь если раньше работало, а теперь нет, — значит, кто-то испортил ситуацию. Возможно, вендор (но это не точно). 

Технический — в нормальном процессе есть обязательные этапы — тестирование, код-ревью, архитектурный контроль. Но в режиме подвига эти шаги пропускаются, а результат закономерен.

Однако полностью отказаться от подвигов нельзя — они неизбежны. Хорошо, что платформа позволяет быстро адаптироваться. Например, хэкнуть Windows или Microsoft Office вряд ли получится, а Luxms BI можно, платформа очень гибкая.

Главное — всегда возвращаться и понимать, что единой работающей системы не будет, если каждый раз не проходить все правильные процессы и процедуры IT-производства. После подвига не просто выдохнуть и праздновать успех, а выполнить пропущенные шаги:

  • Полноценное тестирование;

  • Код-ревью;

  • Рефакторинг кода и/или его оптимизация;

  • Документирование;

  • Информирование команды о технических изменениях.

Код-ревью: не про красоту, а про совместимость

Код-ревью в данном контексте — это не про обучение стилю, это не про поиск ошибок и не про оценку качества кода с точки зрения «красоты». Это инструмент, который позволяет вендору дать рекомендации, объяснить, какие методы лучше заменить, какие подходы стоит пересмотреть, чтобы код продолжал работать в будущем. Это не тренировка, а практическая необходимость.

Основная цель — проверка кода на совместимость с текущими и запланированными изменениями платформы. У вендора есть своя дорожная карта, стратегия развития продукта, идеи по улучшению кодовой базы. Какие-то части кода планируется развивать, какие-то — наоборот, исключать как устаревшие или неудобные. Эти вещи даже не всегда отражены в документации, но они критически важны для долгосрочной работоспособности системы.

Поэтому наша команда в рамках технического аккаунт-менеджмента проводит аудит кода с фокусом на его совместимость с коробочным решением вендора и его будущими версиями. Параллельно команда заказчика и сама может (и должна!) проводить код-ревью с точки зрения совместимости с уже существующими своими кастомными разработками. Ведь у заказчика могут быть свои внутренние стандарты, стайл-гайды, библиотеки кастомных компонентов, специфические темы оформления. Вендор не знает о них, но они могут существенно повлиять на стабильность и поддержку системы в будущем.

Две команды могут объединить усилия, дополнив пропущенные шаги в разработке, чтобы избежать проблем в перспективе. Систематическое игнорирование таких проверок приведёт к тому, что очередной «подвиг» по доработке системы станет невозможным или крайне болезненным.

В мировой практике давно используются методы совместной разработки, включая конференции, открытые репозитории кода и совместное написание программных решений. Открытые форумы, митапы и другие форматы обсуждения позволяют не просто обмениваться общими идеями, но буквально разбирать код построчно, анализируя и оптимизируя решения.

Лучшие практики совместной разработки применяются уже десятилетиями, как в open-source проектах (PostgreSQL и другие), так и в коммерческом ПО.

Оптимизация и проектная поддержка

Разработка кастомных решений часто связана с необходимостью оптимизации. Однако важно понимать, что подходы к улучшению производительности могут быть разными.

Пример: команда заказчика замечает, что дэшборд медленно загружается, и предполагает, что проблема в JavaScript. Разработчики приходят к специалисту, и он ускоряет JavaScript-код в 2 раза. Вроде бы задача решена.

Но можно рассмотреть эту проблему комплексно. В команде вендора есть специалисты разного профиля — дата инженеры, фронтенд и бекенд-разработчики, техподдержка.

Они могут проанализировать не только код, но и архитектуру решения в целом. Оказывается, что вместо ускорения JavaScript в 2 раза можно оптимизировать обработку данных и ускорить работу в 100 раз, устранив проблему на более глубоком уровне.

Есть и ещё момент: команда вендора уже сделала несколько сотен проектов, и может сравнивать один с другим. Например, если у всех запросы выполняются за полсекунды, а у конкретного заказчика — за 30 секунд, это аномалия. Клиент может этого не замечать, потому что не видел другого примера работы, но вендор-то видел, он может выявить узкие места и предложить решения.

Комплексное сопровождение проекта включает:

  • поддержку всего кода, в том числе ETL и кастомных решений;

  • анализ влияния изменений после обновления системы;

  • обеспечение целостности продукта в процессе его развития.

Таким образом, в рамках вендорского сопровождения проекта заказчик получает не просто локальные исправления кода, а целостный подход к решению проблемы, что позволяет добиться максимальной производительности и устойчивости системы.

Кстати, часто заказчики сами находят новые подходы, которые мы не предусматривали. Если они грамотно вписываются в архитектуру, мы берём их на вооружение и распространяем среди всех пользователей. Это напоминает open-source: сообщество помогает улучшать продукт.

Документация и внутренняя инженерная память

Одно из главных условий устойчивого развития кода — наличие понятной, актуальной документации. Сложные системы редко разваливаются из-за багов — гораздо чаще причиной становится потеря контекста. Кто-то ушёл из команды, кто-то «написал быстро и потом забыл», кто-то добавил особенное поведение без комментариев, полагаясь на то, что «и так понятно». Через полгода становится совсем непонятно. Именно поэтому мы всё чаще говорим не только о коде, но и о внутренней инженерной памяти. Даже сильная архитектура со временем начинает «трещать», если её сопровождают только фрагментарные комментарии в стиле // костыль, не трогать.

Важно фиксировать не только что делает код, но и почему он делает это именно так. Особенно когда речь идёт о нестандартных решениях, работе с кастомным LPE, обходах ограничений, асинхронной логике или взаимодействии между несколькими слоями системы. У каждого такого решения должна быть своя «анатомия»: что было до, зачем понадобилось менять, что пробовали, на чём остановились, и какие риски это в себе несёт.

Чтобы не терять эти знания, необходимо создавать:

  • Краткие описания к ключевым техническим решениям — фиксируем суть подхода, особенности реализации и важные нюансы, на которые стоит обратить внимание при повторном использовании или доработке;

  • Методические материалы по развитию внутренних компонентов — описываем принятые практики: как структурировать код, как работать с переменными, как управлять доступами, каких подходов стоит избегать и т.д.;

  • Сборник кейсов из практики — документируем, как мы решали конкретные задачи, какие трудности возникали, какие подходы оказались эффективными, а какие — нет.

Кроме документации к коду, важны и более широкие методические материалы: гайды по архитектуре, правила написания кастомных компонентов, типовые ошибки при работе с LPE, рекомендации по работе с правами и конфигурациями. Это помогает не только ускорять разработку, но и формирует общее инженерное поле — когда каждый следующий шаг строится на понятной и уже проверенной базе. Это не формальность. Это способ защищать себя в будущем от хаоса. Он позволяет быстрее вводить новых людей, смелее рефакторить, видеть, как работает система.

Разумный рефакторинг: как мы переписали логику для высокой производительности

В одном из наших проектов данные изначально хранились в PostgreSQL. На определённом этапе было принято решение перейти на Greenplum — решение класса MPP, более подходящее для анализа больших объёмов данных.

Этап 1. Перенос на Greenplum: функциональная адаптация

У Greenplum и PostgreSQL есть ряд отличий, особенно в поддержке SQL-синтаксиса и оптимизации запросов. На первом этапе задача была не ускорять, а обеспечить корректную работу на новой СУБД.

Что пришлось изменить:

  • Переписаны SQL-запросы, использовавшие конструкции, не поддерживаемые Greenplum. Например, в PostgreSQL активно использовался INSERT ... ON CONFLICT ... DO UPDATE — удобный способ делать upsert. Однако Greenplum такую конструкцию не поддерживает из-за своей распределённой архитектуры;

  • Упрощены участки логики, завязанные на специфичные для PostgreSQL особенности;

  • Перепроверена корректность агрегаций и расчётов после миграции.

На этом этапе приоритетом была функциональная совместимость — важно было, чтобы дэшборды продолжили работать корректно.

Этап 2. Оптимизация под реальные объёмы

На этом этапе оказалось, что объём данных существенно вырос по сравнению с тестовой средой. Это привело к резкому увеличению времени расчётов.

Основные проблемы:

  • Одним из источников данных была сверхбольшая таблица, к которой шло множество запросов при расчётах;

  • Последовательность вычислений была неэффективной;

  • Объём промежуточных данных значительно вырос, что сильно увеличило требования к ресурсам.

Что сделали:

В расчётах использовали временную таблицу, куда подгружали часть данных из большого источника. Таблица хранилась в оперативной памяти, что значительно ускорило расчёты.

Перестроили последовательность вычислений, оптимизировали алгоритмы обработки: порядок расчётов по сущностям исходя из понимания базы.

Результат:

  • Скорость выполнения запросов выросла более чем в 10 раз (с часов до нескольких минут);

  • Повысилась стабильность выполнения запросов даже под высокой нагрузкой.

Развитие системы и управление изменениями

Наша платформа постоянно развивается, компоненты системы эволюционируют. Однако новый функционал также не выходит сразу в идеальном виде — он проходит несколько итераций, получает доработки на основе обратной связи. Это нормальный процесс.

Мы предупреждаем клиентов: если кастомный код использует новые возможности, это дополнительная зона для код-ревью, тестирования и мониторинга. Luxms BI — живой продукт.

Большинство изменений происходит незаметно для пользователей. Однако если клиент использует кастомный код, который напрямую взаимодействует с конфигом, могут возникнуть нюансы. Например, если система начала писать настройки в новом формате, а кастомный код использует старый, это может привести к нестыковкам. Мы стремимся минимизировать влияние таких изменений, но отказ от устаревших решений — естественная часть развития системы: свой код мы также постоянно рефакторим и улучшаем.

Оптимизация кода и архитектурные улучшения в Luxms BI

Один из кейсов по оптимизации кода внутри Luxms BI связан с компонентом VizelTabs. Этот компонент реализует логику визеля-контейнера, в котором дочерние компоненты рендерятся в зависимости от того, какая вкладка открыта.

Компонент очень старый, видны следы работы разных разработчиков (некоторые из них уже даже не работают в команде), и это хорошо видно по структуре кода — фрагменты логики накладывались ситуативно, поверх уже существующего функционала, и код не просто спагеттизировался, но и вился в дреды.

Компонент в какой-то момент начал получать настройки, рассчитанные внутри lpe-выражений. Стал реагировать на смену URL и даже фильтров. Появилась однотипная логика для условно-скрытых вкладок, их заголовков.

Особенность этого компонента ещё и в том, что дочерние элементы — это не реальные дэши, а просто явно указанный перечень JSON-конфигов визелей. Они наследуют права родителя в виде Вкладки и Доски.

В какой-то момент количество различных if-ов достигло критической массы.

Мы всё чаще сталкивались с ситуацией, когда поведение компонентов зависело от логики, рассчитанной через LPE, и быстро меняющегося контекста, который нужно пересчитывать в lpe выражениях. В результате приходилось вручную подписываться на множество различных сервисов и следить за изменениями их моделей — это важно для правильного расчета контекста lpe и корректного поведения компонентов, в частности вкладок. Со временем такая система подписок превратилась в настоящий архитектурный ад.

Было принято решение реализовать иной подход к наблюдению за меняющимся контекстом lpe — так называемые LPE-стримы. Это сложная система на основе промисов, которая хранит все компоненты-подписчики, чей lpe использует сервисы или переменные what-if, от значения которых зависит расчёт соответствующего контекста. Как только модель загружает данные, выходит из состояния loading и завершает обновления, система оповещает: контекст изменился, пора пересчитать значения.

Такой подход стал применяться не только для компонента VizelTabs — он постепенно распространяется на всё большее число коробочных элементов. Это позволяет выстраивать более предсказуемую событийную архитектуру.

Сам компонент VizelTabs был переписан: его размер стал меньше, функции — переиспользуемыми, а логика подписки на смену табов и расчет свойств hidden (видна ли вкладка) и title (название вкладки) реализованы через LPE-стримы. Также в компоненте появилась возможность использовать функции из lpeForReact — вместо строки можно писать результат связки React-нод через встроенные функции, такие как img, svg, wrap и других.

Кроме того, для обратной совместимости сохранена поддержка children, но ранее фиктивные дэши теперь превращаются в настоящие. Это значит, что для них можно задавать собственную ролевую логику, отличную от родителя.

Вместо морали —  немного эмпатии

Когда мы приходим на проект «оптимизировать», мы находимся в лучшей позиции:

  • Уже есть все данные;

  • Понятны бизнес-требования;

  • Видны типовые сценарии использования;

  • Есть профили нагрузки;

  • Фокус на улучшение, а не на «собери хоть что-нибудь к утру».

Писать книгу с чистого листа — одна задача, а редактировать — другая. Конечно, во втором случае видно ошибки, несогласованности, можно предложить лучшие формулировки. Но это другая фаза работы!

Сказать «здесь всё неправильно» — легко. Гораздо сложнее — уважать работу, сделанную в других условиях, и помочь её улучшить.

Если вы — тот, кто собирает первую версию, — не стыдитесь черновиков. Не бойтесь «некрасивого» кода. Это часть жизни проекта. Просто важно помнить, что рано или поздно придёт следующая стадия. И к этому надо быть готовыми: оставить документацию, подумать о расширяемости, предусмотреть возможные изменения.

Проекты с данными — это не гонка на 100 метров. Это длинная дистанция. С остановками, передышками, переосмыслением и вторым дыханием. И каждый этап здесь важен. И первый, и последний.

Хорошая система — это не та, где сразу всё сделано идеально. Это та, которая способна адаптироваться, улучшаться и справляться с ростом требований. Именно поэтому в BI-проектах важно не просто «сделать красиво», а выстроить процесс развития. Шаг за шагом.

Теги:
Хабы:
+3
Комментарии1

Публикации

Информация

Сайт
luxmsbi.com
Дата регистрации
Дата основания
2012
Численность
101–200 человек
Местоположение
Россия