Как улучшить legacy-код

Original author: Jacques Mattheij
  • Translation
Это случается хотя бы раз в жизни каждого программиста, менеджера проекта или тимлида. Вы получаете целую кучу парного навоза. Если повезёт, то всего несколько миллионов строк. Первоначальные авторы давно улетели в тёплые страны, а документация, если она имеется, безнадёжно устарела.

Ваша задача: выбраться из этого бардака.

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

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

Резервная копия


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

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


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

Не трогайте БД


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

Напишите свои тесты


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

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

Сбор метрик и логов


Если старая платформа ещё доступна для разработки и оснащения. Сделайте это в полностью новой таблице БД, добавьте простой счётчик на каждое событие, какое только придумаете, и простую функцию, которая будет увеличивать эти счётчики, основываясь на имени события. Таким способом вы можете реализовать журнал событий с временными метками всего несколькими дополнительными строчками кода и получите хорошее представление, как много событий одного типа ведут к событиям другого типа. Один пример: пользователь открывает приложение, пользователь закрывает приложение. Если два события ведут к неким вызовам бэкенда, то между этими двумя счётчиками в долговременной перспективе должна оставаться постоянная разница, эта разница представляет собой количество приложений, которые открыты в данный момент. Если вы видите намного больше открытий, чем закрытий приложения, вы знаете, что должен быть способ, которым приложение завершает работу (хотя бы сбой). Для каждого события, какое вы найдёте, существует какая-нибудь связь с другими событиями. Обычно вы будете прилагать усилия к достижению постоянных взаимоотношений, за исключением очевидной ошибки где-нибудь в системе. Вы будете стремиться уменьшить те счётчики, которые соответствуют ошибкам, и максимально увеличить счётчики вдоль по цепочке до показателя, заданного в начале. (Например, пользователи, которые пытаются оплатить покупку, должны привести в итоге к полученным платежам).

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

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

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

Меняйте только одну вещь за раз


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

Изменения платформы


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

Изменения архитектуры


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

Пользователь HN mannykannot обратил внимание — справедливо — что не всегда есть такая возможность, и если вам особенно не повезло, то придётся копнуть глубже, чтобы иметь возможность произвести какие-либо архитектурные изменения. Я с этим согласен и должен был упомянуть об этом, так что поэтому здесь это маленькое дополнение. Ещё хотел бы добавить, что если вы производите одновременно высокоуровневые и низкоуровневые изменения, хотя бы постарайтесь ограничить их одним файлом или в худшем случае одной подсистемой, чтобы ограничить объём изменений насколько возможно. Иначе будет очень тяжело, когда придётся производить дебаггинг изменений, которые вы только что сделали.

Низкоуровневый рефакторинг


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

Исправление багов


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

Обновление базы данных


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

Выполнение по дорожной карте


Поздравляем, вы вне опасности и готовы к внедрению новой функциональности.

Никогда даже не думайте о масштабной переделке


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

Так что, как альтернатива, работайте пошагово


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

Релиз!


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

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

Используйте прокси для своей выгоды


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

Да, но всё это займёт слишком много времени!


Ну, как посмотреть. Действительно, если следовать этим шагам, то нужна некоторая повторная работа. Но это действительно работает, и любая оптимизация этого процесса предполагает, что вы знаете о системе больше, чем вы вероятно знаете на самом деле. У меня есть репутация и я действительно не хочу негативных сюрпризов во время работы вроде этой. Если не повезёт, это может привести компанию к провалу или может возникнуть реальная угроза внести беспорядок в работу клиентов. В такой ситуации я предпочитаю полный контроль и железобетонный процесс, а не попытку сэкономить парочку дней или недель, что подвергает угрозе успешный исход. Если вы более ковбойского характера — и ваш начальник согласен — тогда может быть приемлемым взять больше рисков, но большинство компаний выберет слегка более медленную, но намного более верную дорогу к победе.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 16

    +10

    Капитан, капитан, улыбнитесь.


    (Тем не менее, спасибо за перевод)

      +1
      Спасибо за перевод!
      Кто-нибудь может поподробнее пояснить раздел «Оснащение и журналирование»? Какие-то статьи на тему, примеры или ключевые слова для запроса в поиск?
      Звучит, вроде, логично, но всё ещё не понятно как это работает.
        +1
        За такой перевод надо руки вырывать.
        В оригинале это звучит как
        Instrumentation and logging

        Instrumentation — имеется в виду сбор метрик.
        Logging — сбор и анализ логов приложения

          +1
          Перевод не хорош, но «прорекламировал» статью и я её прочитал в оригинале. За то и спасибо.

          Но вопроса это таки не отменяет. В статье пример с открытием/закрытием [десктопного?] приложения.
          Так как я этим методом со счётчиками никогда не пользовался, то было бы интересно посмотреть как его используют в веб-разработке на серверных частях.
          Примеры типа «давайте напишем юнит-тесты на привет-мир или калькулятор» не помогают пониманию.
            +1
            Хм, попробую, с вашего позволения.

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

            Более изощренная инструментация (гуглить real user monitoring) позволит увидеть как быстро открывается страница у пользователя, какие ошибки выскакивают, как часто.

            На бек энде:
            Сбор метрик, опять же, позволяет видеть какие методы дергаются, а какие нет (и может их можно удалить?). Как долго обрабатывается запрос, какие ошибки выскакивают, в каком количестве.

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

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

            Все вместе, позволит понять что, вообще у пользователя творится, ну и в контексте рефакторинга легаси систем — не наломали ли мы чего, после того как чуть чуть потрогали код.

            PS: не сочтите за рекламу, поставил линки на тулы которыми пользовался и остался доволен.
              0
              Спасибо за подробный ответ! И за ссылки – пойду искать аналоги для моих платформ (:

              Если я правильно понял автора, он предлагает расширить применение метрик до ещё одного слоя тестирования доменной логики и поиска ошибок, которые не найдены тестами. Обычно такие метрики сложнее простого счётчика из одной цифры (Яндекс/Гугл-Метрика на фронте, например) и зависят от кучи параметров многих моделей бизнес-логики одновременно.
              Вот об этом было бы интересно почитать, как это организуют более опытные команды ибо я недавно осознал что начал изобретать что-то похожее на DomainEvents с кастомным логированием именно для этих целей.
        +4
        «и в целом вы придёте к заключению, что парни, собравшие старую систему, может быть, не такие идиоты, в конце концов»

        именно, поэтому не надо кидать всё перестраивать, сначала надо понять логику разработчика.
          +1

          Да, заниматься рефакторингом legacy-кода категорически нельзя, если ты внутренне убежден, что написавшие систему — идиоты. Сам грешен и столько раз потом было стыдно, когда через некоторое время понимал, что наворотил более сложную и негибкую систему, чем раньше, или вместе с изменениями угробил красивое решение, которое просто не понял.

        • UFO just landed and posted this here
            +2
            Насчёт копирования при начале — копировать надо реально всё, что можно. Например, может оказаться пропатченной библиотека в /usr/share/lib или в /usr/local/bin окажется бинарник, собранный из исходников в хомяке какого-то пользователя.
              0
              А неплохо создать несколько виртуальных машин для повторяемости результатов разработки и тестирования (а-ля windows xp + visual studio 2005).

              Один раз настроил весь этот зоопарк — и разослал коллегам. По достижению удовлетворительного результата — сохранил снапшот виртуалки.
              +2

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


              Печаль, печаль....


              В целом, что-то в этом есть, хотя имхо можно было бы сократить в несколько раз.

                +1
                Подход с метриками, логами и мониторинг, это конечно хорошо, но не считает ли уважаемая публика, что без static analysis tool понимание что, где, когда работает, не может быть полным. А если что-то работает раз в полгода, так и не поймаешь. Мне интересно знает ли кто-то об оффлайновых инструментах анализа кода. Я вижу, eugenebb, что то делал. Может, поделитесь, какого рода это было? Я думаю, что мы все работаем с легаси и проблема развития и переделки того, что уже никто не может объяснить как написано, становится все актуальней.
                Скоро систему, написанную на Angular и Meteor будут считать легаси, а ей всего три годика от роду… А подумайте банковских системах, которые содержат миллионы строк на КОБОЛЕ?
                  0
                  • UFO just landed and posted this here
                    • UFO just landed and posted this here

                Only users with full accounts can post comments. Log in, please.