В фреймворке Systematic Code and Asset Removal Framework (SCARF) компании Meta* есть подсистема выявления и удаления мёртвого кода.

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

В своём предыдущем посте об автоматическом выводе из эксплуатации продуктов мы говорили о сложностях этого процесса, а также о созданном Meta* решении под названием Systematic Code and Asset Removal Framework (SCARF). В качестве примера мы рассмотрели Moments — приложение обмена фотографиями, запущенное Meta в 2015 году и закрытое в 2019 году. В статье говорится о том, как SCARF помогает в проведении процесса вывода из эксплуатации благодаря его функциям управления рабочим процессом. Мы сказали, что SCARF экономит время разработчиков, выявляя правильный порядок задач для очистки продукта, и указали, что можно заблокировать автоматическую очистку при наличии межсистемных зависимостей. Это естественным образом приводит нас к следующему вопросу: как автоматически разблокировать SCARF при наличии кода, ссылающегося на ресурс?

▍ Удаление мёртвого кода в SCARF


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

▍ Анализ кода


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


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


В SCARF есть поддержка различных языков программирования. Это очен важно, ведь в продуктах Meta* может быть клиентский код, написанный на Java, Objective-C и JavaScript, серверный код, написанный на Hack, а также код бэкенд-инфраструктуры, написанный на Python. Все эти части кода должны удаляться, ведь они комбинируются и формируют один граф зависимостей из-за связи через API и другие виды ссылок.

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

▍ Сборка мусора


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

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

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

Однако увеличение длительности сквозного цикла разработки привело к существенному повышению покрытия. Переход от анализа отдельных идентификаторов к полному графу привёл к увеличению почти на 50% объёма мёртвого кода, удалённого из одной из самых крупных кодовых баз Meta*. Новый подход увеличил видимость состояния наших кодовых баз: объёмов живого и мёртвого кода, количества удаляемых частей на каждом конкретном проходе SCARF.

▍ Тонкая настройка графа зависимостей


Многие из индексируемых при помощи Glean зависимостей относятся к паттернам вызова кода, которые необязательно блокируют удаление этого кода. Например, допустим, у нас есть класс PhotoRenderer, и единственная зависимость в коде от него выглядит так:

if isinstance(renderer, PhotoRenderer):
return renderer.render_photo()
else:
return renderer.render_generic()

В этом случае ссылки на PhotoRenderer и render_photo() можно удалить, после чего код будет выглядеть так:

return renderer.render_generic()

В этом примере класс PhotoRenderer был встроен на основании правила, выведенного из семантики Python: если в коде нет мест, где создаётся экземпляр PhotoRenderer, то мы можем быть уверены, что этот код не может пойти по первой ветви, а значит, он мёртв.

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

uri_dispatch = {
'/home/': HomeController,
'/photos/': PhotosController,
...
}

Если бы мы анализировали только граф зависимостей на уровне языков, то было бы невозможно определить, ссылаются ли где-нибудь на PhotosController, потому что его можно вызвать через этот механизм диспетчеризации URI. Однако если мы знаем из анализа нашего приложения, что в продакшене конечная точка "/photos/" никогда не получает запросов, то можно удалить соответствующий элемент из этого словаря.

Не существует никакого изначального способа извлечь это на основе семантики языка Python, однако наше предметно-ориентированное логирование и дополнение графа позволяют нам сообщать SCARF, что эта операция безопасна.

▍ Автоматизация изменений в коде


Meta* активно занимается автоматизацией изменений в коде. Мы создали внутренний сервис CodemodService, который позволяет инженерам развёртывать конфигурации для масштабной автоматизации изменений в коде. SCARF стал первым примером системы полностью автоматизированных изменений Meta*, распространяющихся на всю компанию; он разрабатывался параллельно с CodemodService. Сегодня CodemodService также обеспечивает сотни других типов автоматизированных изменений в коде, в том числе: автоматизацию форматирования кода, автоматическое удаление завершённых экспериментов, крупномасштабные миграции API и улучшение покрытия строгими типами в языках с частичной типизацией наподобие Python и Hack.

▍ Крупномасштабное удаление мёртвого кода


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


SCARF развился настолько, что способен анализировать сотни миллионов строк кода; за последние пять лет он автоматически удалил более ста миллионов строк кода, сделав более чем 370 тысяч запросов изменений. Отлавливаемые инженерами во время код-ревью ложноположительные срабатывания проверяются и используются для совершенствования анализа SCARF; обычно они отражают в себе новые источники динамического использования, которые должны учитываться в наших расширенных графах. Иногда эти неправильно понятые динамические ссылки могут приводить к ошибочному удалению кода, и такие удаления могут добраться до продакшена. У Meta* есть другие механизмы для отслеживания подобных проблем, и мы относимся к таким инцидентам очень серьёзно.

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

▍ Достаточно ли удаления мёртвого кода?


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

Кроме того, наши системы перестраховываются, выполняя тестовые ссылки на код и данные в системе BigGrep, а не полагаясь полностью на курируемые графы, созданные при помощи Glean и дополнительной информации о динамическом использовании. Этот механизм безопасности помогает не удалять случайно таблицы MySQL, на которые ссылаются по имени на других языках, и предотвращает удаление динамически вызываемого кода в языках наподобие Hack, Python и JavaScript, способных вызывать код через строковые ссылки или использовать eval. Такой подход может вызывать ложноотрицательные срабатывания, но позволяет избегать ложноположительных. При автоматизации удаления мёртвого кода последние представляют более серьёзную проблему.

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

Meta Platforms*, а также принадлежащие ей Facebook** и Instagram**:
* признана экстремистской организацией, её деятельность в России запрещена;
** запрещены в России.


Узнавайте о новых акциях и промокодах первыми из нашего Telegram-канала ????

Комментарии (2)


  1. uuger
    06.11.2023 14:52
    +17

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


  1. Kahelman
    06.11.2023 14:52
    +2

    «В качестве примера мы рассмотрели Moments — приложение обмена фотографиями, запущенное Meta в 2015 году и закрытое в 2019 году.»

    Они в 2019 первую версию фреймворка запустили? А он ещё и из репозитарий все потёр? :)

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