Как называется старый код
Перейти к содержимому

Как называется старый код

  • автор:

Укрощая зверя: 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 повышает шансы. Предоставьте менеджеру отдельный изолированный участок кода, подсистему, которая покрывается тестами, запускается и работает. Это можно сделать, если взять один из наболевших багов, который всплывает с определенной периодичностью и попытаться его отловить и устранить тестом. PoC подтвердит ваши намерения, покажет, что у вас есть план и ваша работа приносит результат.

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

Когда договоримся с менеджером, переходим к тем, с кем приходится работать каждый день.

Коллеги

Не любят перемены. Типичный коллега на типичном 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-канале мероприятия. Скоро увидимся!

  • python
  • legacy
  • рефакторинг
  • тесты
  • smoke tests
  • функциональное тестирование
  • moscow python conf++

О темной стороне legacy-кода. Как решить проблемы с монолитными приложениями

Меня часто просят рассказать о работе с legacy-монолитами. Про микросервисную архитектуру и переход на нее говорят много, но редко упоминают о том, что проекты приходят к этому после многих лет развития как монолиты. Учебники по решению проблем не пишут. Чтобы поменять архитектуру живого решения, надо пройти несколько этапов. Я работал с разными проектами — и с полноценным multitenancy service-oriented REST architecture в Oracle, и с огромным монолитом, в репозитории которого были коммиты за десять лет. Эта статья — о темной стороне, о legacy-коде, и практических решениях проблем с монолитными приложениями на PHP.

Иллюстрация Алины Самолюк

Причины появления

Есть две основные причины появления legacy-кода. Первая — выходят новые версии операционных систем, языков, браузеров, библиотек. Особенно актуальна проблема для мобильных приложений и скриптовых языков — в каждой новой версии платформы нужно исправлять проблемы совместимости со старым кодом. Этого процесса не исбежать.

Вторая — технический долг, который создается специально. Руководство сокращает срок разработки ПО за счет отказа от проектирования, автоматического тестирования или code review, одобряет сторонние библиотеки, которые не поддерживаются, а разработчики не документируют сложную логику. Это происходит повсеместно и не зависит от количества денег в компании. Не стоит ругать плохих начальников. У них есть весомые причины поступать именно так.

У продуктов есть жизненный цикл, период большого спроса на популярные товары длится три-четыре месяца. Все лучшее конкуренты скопируют и сделают еще лучше, поэтому компании вынуждены регулярно выпускать новинки. Чтобы поддерживать объем выручки, новые продукты и новые версии появляются каждые несколько месяцев, так продажи нового цикла компенсируют снижение продаж в конце цикла. По три-четыре крупных релиза в год делают и Apple, и Marvel. И в Oracle на рынке Enterprise SaaS тоже квартальный релизный цикл. При этом рецепта успеха не существует. 97% стартапов выкидывают свои наработки и пробуют делать что-то новое, прежде чем найдут такой продукт, который у них будут покупать. Поэтому затраты на разработку MVP в стартапах максимально сокращают.

Проблемы с легаси? Значит, вам повезло!

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

Проблемы?

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

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

Что делать тем, кому повезло?

Начинать надо с тестирования

Серьезные изменения кода всегда порождают неожиданные проблемы. Без надежного тестирования сбои приложения приведут к потере выручки и снижению продаж.

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

Обновление версии языка

Через несколько лет после написания код становится несовместимым с актуальной версией языка, и это приводит к целому вороху проблем.

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

Составить список проблем совместимости с новой версией PHP помогут утилиты статического анализа.

  • Rector — решит простые случаи несовместимости с новой версией, автоматически обновив часть кода.
  • Exakat — проанализирует совместимости кода по версиям PHP, покажет список используемых расширений, проблемных участков кода и поможет составить список задач на доработку.
  • Phan — покажет в коде лексические конструкции, которые убраны из новых версий PHP.

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

Обновление версии платформы или языка в таком случае выполняется быстро. Автор был инициатором обновления PHP с версии на для приложения с большим объемом кода. И эту задачу команда успешно выполнила за три недели.

Переход от монолита к сервисной архитектуре

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

Самый модный и дорогой путь — параллельная разработка сервисов. Одновременно с поддержкой старого работающего решения создают новые сервисы, зачастую на новом языке — например, на Golang. Главная проблема — это риск, что создать замену не получится. За время разработки сервиса основное приложение меняется, и новый сервис не догонит программу по требованиям. Оценить этот риск непросто.

К счастью, слона можно съесть по кусочкам: отделять от монолита модули, не переписывая код заново, зафиксировать API, а затем превращать их в сервисы. Сначала части кода приложения надо выделить в отдельные пакеты, затем из пакетов можно будет создавать сервисы.

Перенос кода в пакеты открывает ряд возможностей:

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

Главное — это относительно небольшая по объему работы задача. Вынести часть кода в пакет без переписывания можно за несколько дней. У автора был опыт переноса в пакеты по тысяче строк кода в день с инверсией внешних зависимостей. А после фиксации API-модулей будет проще заниматься масштабным рефакторингом.

Разделение приложения на пакеты

Допустим, есть приложение на PHP, которое предоставляет клиентский API. Начинать любые изменения надо с тестирования и релиза, который включает план отката. Это называют release, control, validation и DevOps. Однако в активно развивающихся проектах тестирование и выкладка отработаны. В этом случае надо начинать разделять приложение с определения таких ограниченных контекстов, которые логично выделить в отдельные модули и сервисы.

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

Создание отдельного модуля — это цикл из пяти подзадач:

  1. Выбрать небольшой функционал для переноса в модуль. Например, изменение размера изображений.
  2. Определить API модуля — написать интерфейс, доступный приложению.
  3. Написать или проверить приемочные тесты, например на загрузку и валидацию изображения.
  4. Скопировать в модуль старый код и инвертировать в коде модуля зависимости через границу модуля, без рефакторинга или переписывания всего кода.
  5. Заменить в коде приложения прямые обращения к старому коду на вызовы сервиса из нового модуля. Для решения этой задачи используют две технологии: IoC-контейнер и менеджер зависимостей.

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

Начать создавать пакеты можно в локальном каталоге, а для полноценной сборки и развертывания стоит создать собственный репозиторий пакетов, такой как Packeton, и перенести код модулей в собственные Git-репозитории. Также использовать платный репозиторий Private Packagist.

Как создать composer-пакет в приложении и зарегистрировать его как сервис в IoC-контейнере, смотрите здесь: до изменений, после изменений, diff.

В примерах используется composer для управления зависимостями пакетов и Symfony Dependency Injection как IoC-контейнер для сервисов. У вас может быть другой контейнер. Если в приложении нет IoC-контейнера, придется делать рефакторинг и реализовать внедрение зависимостей. Простейший пример добавления IoC-контейнера в приложение.

Решение проблем со связанностью кода

Есть два типа связанности:

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

Рассмотрим случаи связанности кода и варианты выделения модулей без трудоемкого рефакторинга всего кода.

1. Расширение классов, реализация интерфейсов, использование трейтов, когда декларация структур используется «через границу» будущего модуля. Приведу пример устранения связанности при наследовании, когда родительский и дочерний классы вызывают методы друг друга: результат, diff.

Основные алгоритмы расцепления связанности:

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

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

2. Статические вызовы. Синтаксис PHP допускает вызов статических методов у объектов как методов класса (пример). Если выносите в пакет обычную функцию или класс, у которого есть статический метод, эти функции/методы нужно добавить в публичное API пакета (пример, diff).

Аналогично статические вызовы из пакета к методам классов приложения можно заменить статическими вызовами сервисов. Это будет реализация паттерна «мост».

Ссылки: пример прямого статического вызова, пример инверсии зависимости статического вызова через внедрение сервиса, diff коммита.

Если несколько методов из разных классов используются вместе, для них можно создать сервис-фасад.

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

Если есть несколько независимых классов-«хелперов» (пример) или обычных пользовательских функций, которые используются одновременно и в приложении, и в новом модуле, из них стоит создать отдельный composer-пакет и указать его в зависимостях приложения и других пакетов.

5. Применение глобальных констант и констант классов. Возьмем пример: в приложении есть класс, который нарушает Single Responsibility Principle и содержит обращения к константе другого класса. Наша задача — вынести первый класс в пакет без рефакторинга второго класса, потому что рефакторинг потребует изменения всего кода, в котором используется константа. Надо избавиться от прямого обращения к константе.

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

6. Динамическое разрешение имен через строковые операции. Пример:

$model = new ($modelName . ’Class’);

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

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

Оптимизация

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

Есть несколько способов решения этой задачи:

  1. Сервисы, которые передаются в пакет, можно объявить как lazy.
  2. Объект API пакета можно объявить как Service Subscriber.
  3. Разделить API пакета на несколько сервисов.

Самый гибкий способ — это реализация Service Subscriber. Когда сервис объявляется подписчиком, можно реализовать в пакете вызов внешних сервисов по мере обращения к ним. Примеры: код до изменений, где используется один из нескольких классов, и код после переноса в пакет c инверсией зависимостей, где нужный сервис создается по требованию. Diff.

Service-Oriented Architecture

Хорошо, разделили код на пакеты, но при выкладке все собирается в одно приложение и работает в одном процессе, как монолит. А где же сервис-ориентированная архитектура? До нее еще долгий путь.

У каждого пакета зафиксирован публичный API. На основе этого API можно создать сервис с RESTful-протоколом. Код нового сервиса — это код пакета, вокруг которого написан стандартный роутинг, записываются логи и прочий инфраструктурный код. А в старом коде вместо кода пакета появляется адаптер для HTTP-вызовов через curl.

При создании отдельных внутренних приложений-сервисов надо решить две задачи:

  1. Детальное протоколирование вызовов всех сервисов. Каждому клиентскому запросу надо присваивать уникальный ID вызова, который передается во все сервисы при вызовах внутренних API. И каждый вызов сервиса следует протоколировать. Необходимо иметь возможность отследить вызовы сервисов по цепочке.
  2. Гарантировать единственный результат выполнения запроса при сбое одного из сервисов, когда запрос к сервису передан заново. Пример: клиентский запрос на платеж с его счета на другой счет. При сбое внутреннего выделенного сервиса, который выполняет запись результатов транзакции и пересчитывает баланс на счетах пользователей, повторный запрос к нему не должен привести к двум денежным переводам с одного счета на другой.

Заключение

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

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

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

�� Подобається Сподобалось 4

До обраного В обраному 4

Что такое легаси в программировании

что такое легаси

Легаси-код – (от англ. legacy – наследие) это код, который одна команда разработчиков получила в “наследство” от предыдущей. Это общее усредненное определение, которое характеризует термин легаси-код.

Легаси – термин, который трактуют по-разному

Как можно было догадаться по заголовку, это еще не все: у данного термина с десяток определений, которые плюс-минус все вертятся вокруг феномена “старого” кода, вот, например, парочка популярных:

Легаси – это устаревший код, который больше не поддерживается, не обновляется, но используется;

Легаси – не покрытый тестами или документацией код, написанный очень давно, настолько давно, что никто не знает как он до конца работает.

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

Общие характеристики или как появляется легаси

Не нужно чтобы все пункты совпали, достаточно одного.

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

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

В-третьих, код, который оставлен лишь для обратной совместимости: для поддержки старых версий программ или операционных систем.

Что значит “работа с legacy” в вакансиях

Это значит, что программисту будет весело и прикольно (нет) работать со старым куском кода, о котором даже сеньор, со стажем работы в несколько лет в рамках этой компании, ничего не сможет рассказать. Очень частое явление в мире бекенд разработки на Java или iOS разработки на Objective-C и Swift. В первом случае программист будет обслуживать огромный энтерпрайз бог знает когда и кем написанный. Во втором программиста ждет увлекательный процесс переписывания программы с Objective-C на Swift с обязательным порождением новых багов фич. К слову, за работу с легаси платят в разы больше, чем обычно, хотя не всем это все равно приходится по душе.

Что такое легаси в коде

Иногда программисты на вопрос, почему программа работает именно так, отвечают, что это «легаси» и исправить ничего нельзя. Разберёмся, что это значит, насколько это мешает разработке и что делают с легаси-кодом.

Что такое легаси

С английского legacy переводится как «наследие». Легаси-код — это код, который перешёл «по наследству» от предыдущих разработчиков. Чаще всего это происходит так:

  1. Команда делает продукт, внутри много разных возможностей.
  2. Часть функций со временем оптимизируется, а часть остаётся неизменной в виде старого кода, потому что и так работает.
  3. Некоторое время спустя в команде не остаётся тех, кто писал старый код.
  4. Текущая команда не знает, почему старый код написан именно так.
  5. В этих кусках сложно что-то поменять или разобраться, потому что всё остальное написано уже по-другому.
  6. Этот старый код, который сложно поддерживать и сложно разбираться — это и есть легаси.

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

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

Откуда берётся легаси

Причин появления легаси может быть несколько:

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

Легаси — это не какое-то преступление, а часть жизни любой живой ИТ-компании. Рано или поздно у любого продукта появится легаси. И чем крупнее проект, тем больше его будет. Например, в исходном коде Windows 10 до сих пор остаются фрагменты кода, написанные ещё 20 лет назад для Windows 3.1.

Легаси — это плохо?

Легаси — это просто старый код, который нужно поддерживать наравне с новым. Если он работает — отлично, пусть живёт. Другое дело, что команде, например, было бы удобнее, чтобы код был написан не на старом фреймворке, а на новом, который знают все.

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

Что значит «поддерживать старый код»?

Например, в старом коде для запроса к серверу идёт сначала адрес, а потом номер запроса. Спустя 10 лет требования сервера изменились, поэтому сначала должен идти запрос, а потом уже адрес. Значит, нужно изменить порядок полей в коде.

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

Что делать с легаси-кодом

Если легаси-код работает и не требует вмешательства и поддержки — то можно пока ничего не делать, пусть работает. Будет время — перепишем на новый фреймворк, а если нет, то и так пока поработает.

А если нужно срочное вмешательство — пахнет бедой. Зовите менеджеров.

Курсы по программированию с нуля

Приходите к нам в ИТ. У нас есть удаленная работа, высокие зарплаты и удобное обучение в «Яндекс Практикуме». Старт бесплатно.

Курсы по программированию с нуля Курсы по программированию с нуля Курсы по программированию с нуля Курсы по программированию с нуля

Получите ИТ-профессию

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *