Как инженеры перестраивают банковские системы без остановки для миллионов пользователей

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

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

О том, как перестраивать критические части ИТ-систем непосредственно во время их работы, почему старый код не всегда является плохим и что происходит, когда инженер пытается изменить основу продукта, не затрагивая его пользователей, «Экспресс газета» поговорила с Сергеем Подгайным, ведущим iOS-инженером одного из системно значимых банков страны.

— Сергей, почему вообще возникает ситуация, когда работающий код становится проблемой?

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

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

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

— Почему тогда нельзя просто переписать проблемную часть заново?

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

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

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

— А как это выглядит на практике?

Сергей Подгайный: Сначала нужно понять, что именно в старой части мешает развитию продукта. Затем выделить конкретную задачу и сделать для нее новое решение. После этого постепенно переводить на него существующие возможности.

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

— Какой из таких случаев был для вас самым сложным?

Сергей Подгайный: Один из наиболее показательных случаев связан с внутренней системой, которая отвечает за построение интерфейса приложения. Она позволяет формировать экран на стороне сервера: сервер передает приложению описание того, какие элементы и в каком порядке нужно показать пользователю.

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

— На первый взгляд решение кажется простым: убрать это дерево и сделать всё по-другому?

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

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

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

— Получается, даже в этом случае проблема была не просто в «плохом старом коде»?

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

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

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

— То есть найти техническую проблему зачастую проще, чем понять, почему система устроена именно так?

Сергей Подгайный: Да. Увидеть неудачное место в коде обычно несложно. Сложнее понять его связи с остальной системой. В старом коде могут быть решения, смысл которых уже не очевиден. Возможно, их приняли несколько лет назад из-за требований, которых сейчас уже нет. А возможно, за внешне странным решением скрывается важная функция, о которой легко забыть.

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

— Как понять, что старую часть системы действительно пора перестраивать?

Сергей Подгайный: Один из главных признаков – стоимость изменений начинает расти быстрее, чем сложность самих задач. Например, небольшая новая функция требует изменений сразу в нескольких, на первый взгляд не связанных между собой частях приложения. Оценки постоянно увеличиваются из-за побочных эффектов. Перед выпуском приходится проводить всё больше дополнительных проверок. Разработчики начинают избегать определенных участков кода, потому что любое вмешательство может вызвать непредсказуемые последствия.

Еще один тревожный сигнал – когда знания о важной части системы сосредоточены у одного-двух человек. Если команда регулярно говорит: «Сейчас лучше сюда не лезть», и это продолжается квартал за кварталом, техническая проблема постепенно становится проблемой самого продукта. В какой-то момент старая система начинает определять, какие возможности бизнес вообще может запускать и сколько это будет стоить. Тогда уже речь идет не просто о неудобном коде.

— А когда компания понимает, что с перестройкой уже опоздала?

Сергей Подгайный: Когда значительная часть времени уходит не на развитие новых возможностей, а на обход ограничений старой системы, исправление последствий изменений и дополнительные проверки. При этом даже в такой ситуации полная замена системы с нуля обычно не становится хорошим решением. Скорее нужно определить самые дорогие и рискованные участки и начинать с них. Если постепенно выносить из старой системы отдельные задачи и переносить их в новые части, можно менять продукт без остановки его работы. Важно не ставить себе цель избавиться от всего старого кода. Цель – сделать так, чтобы систему снова можно было безопасно и предсказуемо менять.

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

Сергей Подгайный: Для пользователя как раз хорошо, если он ничего не заметит. Приложение должно продолжать работать привычным образом. А вот для команды изменения должны быть ощутимыми. Новую функцию должно становиться проще разрабатывать, количество связанных изменений – сокращаться, риски – снижаться. Хороший результат иногда выглядит очень скромно: следующий разработчик просто может спокойно открыть этот участок кода и внести изменение, не опасаясь, что заденет пять других частей приложения.

— Получается, главный враг здесь не возраст кода, а его неспособность меняться?

Сергей Подгайный: Да. Старый код сам по себе не является проблемой. Можно много лет использовать решение, если оно хорошо выполняет свою задачу и не мешает развитию продукта. Проблема начинается тогда, когда устройство системы перестает соответствовать тому, как развивается продукт. В этот момент старую часть можно сохранить, ограничить ее влияние или постепенно заменить – в зависимости от конкретной ситуации.

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

— И эта задача характерна не только для банковских приложений?

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

Василий Черный