Удаление устаревшей функции из кода концептуально является одним из самых простых действий, которые может выполнить разработчик. Удалить определение, убедиться, что её никто не использует, зафиксировать изменения. На практике же для любой функции, которая существовала достаточно долго, чтобы быть объявленной устаревшей, именно на этапе «убедиться, что её никто не использует» процесс дает сбой. Функция может вызываться из кода, написанного много лет назад кем-то, кто больше не работает в команде, из репозитория, который редко вносит изменения, из языка или фреймворка, которым текущая команда не владеет. Она может вызываться косвенно через обертку, через рефлексию или через механизм диспетчеризации во время выполнения, который не отображается ни в одном статическом графе вызовов. На неё могут ссылаться в сгенерированном коде, в тестовой структуре или в файле конфигурации, который запускает её по имени. Разработчик, который помечает её как устаревшую, и разработчик, который в конечном итоге её удаляет, могут не иметь возможности узнать обо всём этом без инструмента, способного создать полный, кросс-репозиторный список вызывающих функций.
Прежде чем что-либо удалять, необходимо найти каждого звонившего.
SMART TS XL Создает кроссъязыковой граф вызовов, который идентифицирует каждого вызывающего абонента любой функции до внесения изменений.
Кликните сюдаПоследствия такой ошибки немедленны и конкретны. Функция, удаленная с неполным обнаружением вызывающих сторон, приводит к сбоям во время выполнения в системах, которые все еще от нее зависят. В монолитной системе с одним развертыванием поверхность отказов ограничена. В распределенной системе с множеством сервисов, каждый из которых развернут независимо, сбои распространяются каскадно: сервис, предоставляющий функцию, обновляется, потребители — нет, и сбои проявляются во время выполнения в производственной среде в системах, которые могут принадлежать разным командам. В среде мэйнфрейма, где программы COBOL вызывают общие утилиты по имени, сбой может не проявляться до тех пор, пока не запустится определенное пакетное задание, которое может выполняться еженедельно или ежемесячно, делая отсутствующую ссылку невидимой в обычных циклах тестирования. Как рассматривается в более широком контексте управления устаревшим кодом , риски накапливаются со временем: устаревший код, который удален не полностью, опаснее, чем устаревший код, который остается на месте, потому что удаление создает иллюзию полноты, в то время как оставшиеся вызывающие стороны продолжают работать с определением, которого больше не существует.
Эта статья представляет собой практическое руководство по обнаружению вызывающих функций до их удаления: что требуется для полного перечня вызывающих функций, почему инструменты, к которым разработчики обращаются в первую очередь, структурно недостаточны, как различные типы взаимосвязей вызовов требуют различных подходов к анализу, и как выглядит подлинное межсистемное перечисление вызывающих функций в кодовых базах корпоративного масштаба, которые смешивают языки, платформы и репозитории.
Почему обнаружение абонентов сложнее, чем кажется.
Поверхностный способ обнаружения вызывающих функций знаком каждому разработчику: щелкнуть правой кнопкой мыши по имени функции в IDE, выбрать «Найти все ссылки» или «Показать иерархию вызовов» и просмотреть результаты. Это надежно работает в рамках одного проекта, загруженного в одном экземпляре IDE. Как только кодовая база выходит за эти рамки, результаты становятся неполными, и это не видно в выводе. IDE не указывает, какие вызывающие функции не были найдены, поскольку она не проиндексировала репозитории, содержащие их. Разработчик видит набор результатов, который кажется полным, и продолжает работу соответствующим образом.
Это структурная проблема обнаружения вызывающих функций в масштабе: инструменты, которые разработчики используют наиболее свободно, ограничены областью их индексирования, а в больших распределенных многоязычных системах эта область охватывает лишь малую часть мест, где может быть вызвана данная функция. Уверенность разработчика в полноте результатов обратно пропорциональна фактической полноте поиска. В небольшой одноязычной кодовой базе иерархия вызовов IDE действительно надежна. В корпоративной системе, охватывающей множество репозиториев, языков и сред развертывания, она систематически вводит в заблуждение. Как показано в контексте энтропии кода и риска рефакторинга , устаревшие модули могут зависеть от устаревших интерфейсов, в то время как более новые сервисы по-прежнему вызывают подпрограммы, изначально разработанные для более ранних сред, и именно эти межсистемные связи вызовов не видны при поиске, ограниченном IDE.
Для понимания конкретных причин сбоев в обнаружении вызывающего абонента необходимо изучить каждый основной тип вызовов: прямые вызовы, косвенные вызовы, вызовы между языками и динамическая диспетчеризация. Каждый тип вызова завершается сбоем по разным причинам и требует применения различных методов анализа для корректного решения проблемы.
Прямые звонки через границы репозитория
Прямые вызовы — это простейший тип вызовов: одна функция явно вызывает другую по имени. В рамках одного репозитория IDE обрабатывают такие вызовы надежно. При переходе через границы репозиториев анализ завершается неудачей, поскольку индексация IDE не охватывает эту границу. Если репозиторий A определяет общую вспомогательную функцию, а репозитории B, C и D импортируют и вызывают ее, IDE для любого из этих репозиториев видит только вызовы в пределах своей собственной индексированной области видимости.
Такой шаблон вызова функций из нескольких репозиториев является скорее нормой, чем исключением в микросервисных архитектурах, где общие библиотеки публикуются в виде пакетов и используются десятками сервисов. Разработчику библиотеки, объявляющему функцию устаревшей в общем пакете, необходимо знать, какие из использующих её сервисов всё ещё её вызывают. Его IDE ничего не знает об этих сервисах. Менеджер пакетов знает, какие сервисы зависят от пакета, но не знает, какую именно функцию внутри пакета вызывает каждый сервис. Для преобразования из «этот сервис использует версию X этого пакета» в «этот сервис вызывает эту конкретную устаревшую функцию» требуется индексировать исходный код каждого сервиса и разрешать вызов к конкретному определению функции.
Непрямые звонки: обертки, делегаты и фасады
Функция не может вызываться напрямую её потребителями. Она может вызываться через функцию-обертку, которая обеспечивает дополнительное логирование, обработку ошибок или преобразование параметров. Она может быть назначена делегату или указателю на функцию и вызываться через делегат. Она может быть зарегистрирована в реестре служб или в рамках плагинов и вызываться по имени через механизм диспетчеризации. В каждом из этих случаев прямой поиск вызовов устаревшей функции возвращает неполный результат, поскольку фактические вызывающие функции обращаются к обертке или диспетчеру, а не к самой устаревшей функции.
Вызов функций через обертку особенно распространен в больших кодовых базах, где сквозные задачи, такие как логирование, авторизация и логика повторных попыток, накладываются на основные функции с помощью шаблонов обертки. Устаревшая функция, обернутая утилитой логирования, фактически вызывается каждым вызывающим кодом обертки, а не любым кодом, содержащим имя устаревшей функции. Идентификация этих вызывающих кодов требует трассировки по обертке: утилита логирования вызывает устаревшую функцию, и каждый вызывающий код утилиты логирования, следовательно, является косвенным вызывающим кодом устаревшей функции. Именно этот рекурсивный обход графа вызовов отличает тщательный перечень вызывающих кодов от поверхностного поиска ссылок.
Рассмотрим показательный пример на Java, где доступ к устаревшему методу осуществляется через слой делегирования:
Ява
// Deprecated in the core service
@Deprecated
public BillingResult calculateLegacyFee(Account account) {
// original implementation
}
// Facade that delegates; not visible as a direct caller in a simple reference search
public BillingResult computeFee(Account account) {
return calculateLegacyFee(account); // indirect caller
}
// Actual consumer; calls computeFee, unaware of the underlying deprecated method
public void processMonthlyBilling(List<Account> accounts) {
accounts.forEach(a -> computeFee(a)); // two hops from the deprecated function
}
Поиск по запросу «Найти все ссылки» calculateLegacyFee Возвращает computeFee Это единственный звонивший. Звонок не возвращается. processMonthlyBilling, который является подлинным потребителем устаревшего поведения. Для полного учета вызывающих абонентов необходимо пройтись по графу вызовов вверх по потоку. computeFee чтобы определить каждый путь, который в конечном итоге вызывает устаревший метод.
Межъязыковые обращения
Межъязыковые вызовы — это категория, где стандартные инструменты обнаружения вызывающих сторон терпят наиболее полную неудачу. Когда Java-сервис вызывает программу COBOL по имени через промежуточный слой, когда скрипт Python вызывает хранимую процедуру, которая инкапсулирует устаревшую функцию, или когда задание JCL вызывает программу по ее PROGNAME, которая внутренне вызывает устаревший абзац, ни одна из этих связей не отображается в графе вызовов ни одного отдельного языка. Инструменты каждого языка видят только свою собственную сторону вызова.
В средах мэйнфреймов межъязыковые вызовы носят структурный и повсеместный характер. Поток заданий JCL указывает имя выполняемой программы COBOL. Программа COBOL вызывает абзацы и подпрограммы по имени. Вспомогательные абзацы, определенные в библиотеках копирования, используются многими программами. Когда абзац в библиотеке копирования устаревает, для поиска всех вызывающих его элементов необходимо понимать взаимосвязи вызовов COBOL (какие программы включают библиотеку копирования и какие вызывают абзац), взаимосвязи вызовов JCL (какие задания вызывают эти программы) и любые межъязыковые интерфейсы (Java или SQL, взаимодействующие с этими программами). Ни один инструмент не охватывает все эти взаимосвязи. Как показано в анализе статического анализа устаревших систем , инструменты статического анализа, созданные для современных сред, не могут увидеть полную картину того, как запускаются, вызываются и взаимодействуют устаревшие программы, когда взаимосвязи вызовов одновременно охватывают JCL, COBOL и межсистемные интерфейсы.
Динамическое распределение задач и рефлексия
Некоторые вызывающие программы обращаются к функции не по её прямому имени в исходном коде, а через механизм, который разрешает функцию во время выполнения: рефлексия в Java или .NET. getattr/__call__ в Python, позднее связывание в COBOL через CALL identifierДинамическая диспетчеризация посредством полиморфизма или вызов по строке в плагинных фреймворках и системах, управляемых конфигурацией. Эти вызывающие функции не содержат имени устаревшей функции в какой-либо форме, которую статический анализ мог бы надежно обнаружить.
Конфигурационный файл, в котором имя функции указано в виде строки, загружается во время выполнения и используется для вызова функции посредством рефлексии, является вызывающим объектом, который нигде не встречается при анализе исходного кода. Фреймворк плагинов, который обнаруживает и вызывает зарегистрированные обработчики по интерфейсу, является вызывающим объектом, который появляется в графе вызовов только как вызов механизма диспетчеризации, а не как вызов какого-либо конкретного обработчика. Идентификация этих вызывающих объектов требует сочетания статического анализа для поиска динамических шаблонов диспетчеризации, трассировки во время выполнения для наблюдения за фактическими вызовами и ручной проверки логики конфигурации и регистрации, определяющей, каким функциям осуществляется диспетчеризация. Как обсуждалось в разделе, посвященном анализу статического анализа обфусцированного и сгенерированного кода , когда пути выполнения не выражены непосредственно в исходном коде, статический анализ должен восстанавливать вероятные пути на основе структурных шаблонов, а не прямых текстовых ссылок, и эти восстановления требуют анализа самого механизма диспетчеризации с учетом языка программирования.
Инструменты, к которым разработчики обращаются в первую очередь, и на чём они останавливаются.
Разработчики используют предсказуемую последовательность инструментов при попытке обнаружения вызывающих сторон, и столь же предсказуемый момент, когда каждый из них перестает давать надежные результаты. Понимание этой последовательности важно, поскольку результаты работы каждого инструмента выглядят полными, даже если это не так.
Иерархия вызовов IDE: надежная работа в рамках одного проекта.
Наиболее естественным первым шагом являются функции иерархии вызовов в IDE. IntelliJ IDEA, Visual Studio, VS Code и Eclipse предоставляют те или иные функции типа «найти все вызывающие функции» или «показать иерархию вызовов», которые рекурсивно перечисляют вызывающие функции выбранной функции в пределах индексированной области текущего проекта или рабочей области. Для функции, используемой исключительно в одном репозитории и одном языке, эти функции являются точными и достаточными.
Ограничение явно указано в области видимости: «в пределах индексированной области видимости». Вызывающие функции в других репозиториях, в средах выполнения других языков или в сервисах, которые зависят от этого кода через менеджер пакетов, а не через прямую ссылку на проект, находятся за пределами этой области видимости. IDE не указывает, что именно не было проиндексировано. Разработчик получает набор результатов и не имеет представления о том, сколько дополнительных репозиториев не было проиндексировано, сколько из них используют устаревшую функцию или означает ли результат «ноль вызывающих функций» действительно ноль вызывающих функций или «ноль вызывающих функций в той части системы, которую может видеть этот инструмент».
grep и текстовый поиск: широкие, но структурно нечувствительные.
Когда поиск в IDE предположительно неполный, следующим шагом обычно является текстовый поиск: поиск с помощью grep по доступным каталогам исходного кода или поиск по платформе через поиск кода GitHub или GitLab. Это значительно расширяет область поиска и позволяет найти вызывающие функции в других репозиториях, если эти репозитории доступны. Структурная проблема заключается в том, что текстовый поиск находит строки, а не вызовы. Он возвращает каждое вхождение имени функции, включая комментарии, упоминающие функцию, строки документации, сообщения логов, которые называют функцию для целей отладки, и строковые литералы, содержащие имя функции, но не вызывающие её. Он также пропускает вызывающие функции, где имя функции отличается от строки поиска: вызывающие функции через псевдонимы, через частично совпадающие имена в COBOL, где имена могут быть сокращены, или через динамический вызов, когда имя формируется во время выполнения.
Результаты текстового поиска требуют ручной фильтрации для определения того, какие из них являются фактическими адресами вызовов, какие — ссылками на документацию, а какие — ложными срабатываниями из-за конфликтов строк. В большой системе эта фильтрация сама по себе представляет значительные усилия, и процесс фильтрации не может проверить полноту: если вызывающий абонент был пропущен из-за использования другого имени, отфильтрованный набор результатов не будет содержать никаких указаний на это упущение.
Предупреждения компилятора и @Deprecated Аннотации
Современные языки программирования и инструментальные средства предоставляют механизмы аннотаций устаревания, которые генерируют предупреждения при вызове устаревших функций. В Java, например, такие механизмы существуют. @Deprecated аннотация в сочетании с -Xlint:deprecation Выдает предупреждения на этапе компиляции в местах вызовов. C# [Obsolete] Атрибут генерирует предупреждения во время сборки. Принятое в Go соглашение об именовании устаревших функций и их документировании в godoc не приводит к автоматическому появлению предупреждений. Эти механизмы ценны, но имеют определенные ограничения: они работают только для вызывающих функций, компилируемых с тем же кодом, в котором указано устаревание.
Программа, использующая более старую версию библиотеки, предшествующую указанной. @Deprecated Аннотация не получает никаких предупреждений. Вызывающий код, использующий бинарный артефакт вместо компиляции из исходного кода, не получает никаких предупреждений. Вызывающий код на другом языке, осуществляющий вызов через кросс-языковой интерфейс, также не получает никаких предупреждений. И что особенно важно, предупреждения, генерируемые во время компиляции, являются локальными для представления компилятора: они предупреждают о вызовах, которые он видит, а не о вызовах в других репозиториях, которые компилируются отдельно. Использование предупреждений компилятора в качестве единственного механизма обнаружения вызывающих кодов в многосервисной системе приводит к тому, что пропускается каждый вызывающий код, компилируемый независимо, что является нормальным явлением в микросервисных архитектурах.
Инструменты статического анализа: лучше, но ограничены по своему применению.
Специализированные инструменты статического анализа обеспечивают более точное перечисление вызывающих процессов, чем IDE, для целевого языка, и часто могут пересекать границы репозиториев, если настроены на индексирование нескольких кодовых баз. Они строят корректные графы вызовов, а не полагаются на текстовое сопоставление, лучше обрабатывают псевдонимы и косвенные вызовы, чем поиск в IDE, и могут запускаться в конвейерах CI для отслеживания новых вызывающих процессов по мере их добавления. Это наиболее эффективный подход для одного языка программирования.
Ограничение заключается в той же границе области видимости, что и у IDE, но теперь на уровне инструмента: инструмент статического анализа Java не индексирует программы COBOL, анализатор COBOL не индексирует службы Java, и ни один из них не индексирует потоки заданий JCL. В системе, где устаревшая функция представляет собой утилиту COBOL, вызываемую из программ COBOL, которые запускаются из заданий JCL, и выходные данные которой обрабатываются службами Java, каждый инструмент статического анализа видит лишь один фрагмент связи вызова. Как рассматривалось в контексте основных методов рефакторинга , идентификация всех путей выполнения к заданному участку кода, включая редкие условия ошибок и резервные ветви, требует полного отображения графа вызовов, которое инструменты, работающие на одном языке, не могут построить, преодолевая языковые барьеры.
Что на самом деле требуется для полного учета звонков
Полный перечень вызывающих функций для устаревшей функции в корпоративной системе — это не результат поиска. Это структурированное перечисление всех путей выполнения, по которым можно получить доступ к устаревшей функции, включая прямые, косвенные, межъязыковые и динамически распределяемые пути. Для создания такого перечисления требуется ряд возможностей, которые не предоставляет ни один стандартный инструмент.
Единый кроссъязыковой граф вызовов. Граф вызовов должен охватывать все языки системы. Вызов из процедуры JCL в программу COBOL, вызов из программы COBOL в разделяемую утилиту и вызов из службы Java в ту же программу COBOL через интерфейс промежуточного программного обеспечения должны быть узлами и ребрами в одном и том же графе. Устаревшая функция является узлом в этом графе, а перечисление вызывающего объекта представляет собой обход всех входящих ребер, прямых и транзитивных, независимо от того, на каком языке они исходят.
Рекурсивный обход всего графа вызовов. Прямые вызывающие функции — это только первый слой. Для полного инвентаризации необходимо следовать по графу вызовов вверх по потоку через косвенные вызывающие функции, функции-обертки и фасадные слои, пока обход не достигнет функций, у которых нет собственных вызывающих функций, которые являются истинными точками входа в цепочки вызовов. Каждая функция в пути, который заканчивается на устаревшей функции, является вызывающей функцией в соответствующем смысле: удаление устаревшей функции нарушит все пути, проходящие через нее.
Индексирование между репозиториями. Граф вызовов должен включать код из каждого репозитория, который потенциально может вызывать функцию, включая репозитории, зависящие от функции через общую библиотеку или пакет. Это требует одновременного индексирования всех репозиториев и разрешения связей импорта между репозиториями для соединения вызовов в одном репозитории с определениями в другом.
Выявление шаблонов косвенного вызова. Анализ должен идентифицировать вызовы, совершаемые через рефлексию, динамическую диспетчеризацию, указатели на функции, делегаты и вызовы на основе строк в конфигурационных файлах. Для этого требуется обнаружение на основе шаблонов, а не прямое разрешение границ вызовов: поиск механизмов динамической диспетчеризации в коде и определение того, к каким функциям они могут обращаться при каких условиях.
Различие между активными вызывающими функциями и вызывающими функциями, используемыми только в тестах или являющимися неактивными. Не все вызывающие функции требуют одинакового ответа. Вызывающая функция, существующая только в тестовом наборе для самой устаревшей функции, должна быть удалена в рамках очистки, а не перенесена. Вызывающая функция в коде, который сам был идентифицирован как неактивный код в результате анализа использования, не является препятствием для удаления функции. Понимание этих различий требует объединения перечисления вызывающих функций с информацией о том, какие пути выполнения кода фактически активны. Как подробно описано в разделе об обнаружении неактивного кода с помощью статического анализа , недоступный код и неиспользуемые функции могут сохраняться годами в критически важных системах из-за неполной документации или неопределенности в отношении исторических зависимостей, и инвентаризация вызывающих функций для устаревшей функции должна различать вызывающие функции, которые сами по себе являются активными, и вызывающие функции, которые сами по себе являются неактивными.
Процесс амортизации и последующего удаления: структурированный подход.
Рассматривание удаления устаревших функций как единичного события, а не как структурированного процесса, является причиной большинства ошибок при обнаружении вызывающих функций. Правильный подход рассматривает удаление как заключительный этап многоэтапного процесса, который начинается задолго до удаления какого-либо кода.
Этап 1: Разметка и измерение
Первый шаг — пометка функции как устаревшей с использованием встроенного в язык механизма (@Deprecated на Яве, [Obsolete] в C#, #[deprecated] (в Rust или соответствующем эквиваленте) и установление базового количества вызывающих функций. Этот базовый показатель не является результатом какого-либо отдельного поиска; он является результатом индексации каждого известного кода, который может вызывать функцию, и подсчета результатов. Базовый показатель служит двум целям: он количественно определяет масштаб миграции и предоставляет ориентир, относительно которого можно измерять прогресс по мере миграции вызывающих функций.
Базовые показатели следует организовать по типу звонящего и местоположению:
| Категория вызывающего абонента | Количество | приоритет | Владелец |
|---|---|---|---|
| Прямые вызывающие функции в том же репозитории | N | Высокий | Текущая команда |
| Прямые звонки в службы поддержки зависимых лиц | N | Высокий | Владельцы сервисов |
| Вызовы через функции-оболочки | N | Средний | Владельцы упаковки |
| Вызовы в сгенерированном коде или коде фреймворка | N | Средний | Команда разработчиков фреймворка |
| Вызовы в тестовом коде | N | Низкий | Текущая команда |
| Вызовы в мертвом коде | N | Только уборка | Текущая команда |
Этап 2: Уведомление и миграция
Благодаря полному учету абонентов, миграция становится организованным процессом, а не реактивным. Каждый абонент получает уведомление о конкретном местоположении вызова: не «возможно, вы звоните на эту функцию», а «вы звоните на эту функцию по номеру 247». BillingService.javaстрока 82 из AccountProcessor.javaа также в интеграционном тесте в строке 14 BillingServiceTest.java«Такой уровень детализации возможен благодаря наличию списка вызываемых абонентов, чего не могут обеспечить общие предупреждения об устаревании.»
Предоставление плана миграции вместе с уведомлением имеет важное значение. Уведомление об устаревании должно включать документацию по заменяющей функции, описание любых различий в поведении между старой и новой реализациями, а в случае нетривиальных изменений — пример кода, демонстрирующий состояние до и после. Для вызывающих функций в кодовых базах других команд сроки миграции следует согласовывать явно, а не объявлять в одностороннем порядке, поскольку у этих команд есть свои приоритеты и обязательства по выполнению работ. Как было рассмотрено в контексте рефакторинга баз данных в зависимых системах , поэтапное внедрение потребителей новой структуры до объявления старой устаревшей — это дисциплина, которая предотвращает появление критических изменений как неожиданных инцидентов.
Этап 3: Мониторинг количества звонков
В период между базовым измерением и запланированной датой удаления необходимо непрерывно отслеживать количество вызывающих функций. Каждый раз, когда вызывающая функция переходит на заменяющую функцию, количество уменьшается. Крайний срок удаления наступает, когда количество активных вызывающих функций достигает нуля (вызывающие функции, предназначенные только для тестирования, и функции, находящиеся в состоянии "мертвого кода", могут быть удалены одновременно с самой функцией). Непрерывный мониторинг требует запуска перечисления вызывающих функций по расписанию в рамках конвейера CI, а не полагаться на разовый учет, который устаревает по мере изменения кода.
Мониторинг также отслеживает новые вызовы, добавленные в период устаревания. В крупных организациях часто в процессе миграции создается новый код, вызывающий устаревшую функцию, либо потому что разработчик не знал об устаревании, либо потому что проверка кода это пропустила, либо потому что автоматический генератор кода создает код, вызывающий устаревшую функцию. Обнаружение вызовов на уровне CI для устаревшей функции, настроенное на сбой при появлении новых мест вызова, предотвращает рост количества вызовов во время миграции.
Этап 4: Проверка полноты перед удалением.
Непосредственно перед удалением функции следует еще раз выполнить перечисление вызывающих функций по всему объему известных кодовых баз. Эта заключительная проверка служит своего рода защитным механизмом: она подтверждает, что количество активных вызывающих функций достигло нуля, и выявляет любые добавления на поздних этапах, которые не были обнаружены мониторингом CI. На этом этапе инвентаризация также должна подтвердить отсутствие динамических вызывающих функций: конфигурационных файлов, ссылающихся на функцию по строкам, регистраций на основе рефлексии и любых других механизмов косвенного вызова, выявленных в ходе первоначального анализа.
Проверка должна распространяться на граф зависимостей любых разделяемых библиотек или пакетов, которые предоставляют доступ к устаревшей функции. Если функция является частью общедоступного API, используемого внешними сторонами, сроки удаления должны учитывать внешних потребителей, которые могут быть недоступны через внутренний анализ кода. Для внутренних систем проверка охватывает всю индексированную кодовую базу. Для общедоступных API проверка охватывает известный набор потребителей плюс определенный период прекращения поддержки, в течение которого внешние потребители должны перейти на новую версию.
Чем отличается обнаружение абонента в устаревших и мэйнфреймовых средах.
Описанные выше проблемы применимы к любой крупной программной системе, но они особенно остро стоят в средах мэйнфреймов и устаревших систем, поскольку взаимосвязи между вызовами в этих средах выражаются через механизмы, для анализа которых современные инструменты обнаружения вызывающих абонентов не предназначены.
В средах COBOL функции вызываются с помощью операторов CALL, которые могут ссылаться на целевой объект через строковый литерал, через элемент данных, содержащий имя программы, или через указатель на процедуру. Случай со строковым литералом разрешается с помощью статического анализа; случай с элементом данных требует анализа потока данных для определения того, какое значение может содержать элемент данных в момент вызова; а случай с указателем на процедуру требует отслеживания того, как присваивается указатель. Каждый из этих механизмов вызова выглядит по-разному в исходном коде и требует различного анализа для разрешения.
В средах JCL программы вызываются по имени в операторах EXEC PGM=. Имя программы представляет собой строку, которая сопоставляется с скомпилированным модулем в библиотеке загрузки. Отслеживание вызывающих функций программы COBOL через JCL требует анализа JCL для извлечения имен программ, сопоставления этих имен с скомпилированными программами COBOL, которые их реализуют, и определения того, какие абзацы COBOL в этих программах вызывают устаревшую утилиту. Это многоэтапное решение полностью выходит за рамки возможностей анализатора COBOL или анализатора JCL, работающих изолированно.
В средах COBOL особенно важен случай использования общих копибуков. Устаревший абзац, определенный в копибуке, может быть включен во многие программы с помощью операторов COPY. Абзац физически не дублируется в каждой программе; он включается во время компиляции. Анализ, подсчитывающий количество вхождений имени абзаца в исходных файлах без разрешения включений копибуков, будет как завышать (обнаруживая определение абзаца в самом копибуке), так и занижать (упуская тот факт, что каждая программа, включающая копибук, имеет доступ к абзацу). Для корректного обнаружения вызывающих функций необходимо понимать, какие программы включают какие копибуки и какие абзацы в этих копибуках они фактически вызывают. Взаимосвязь между жестко закодированными ссылками и их нижестоящими потребителями иллюстрирует, почему разрешение этих взаимосвязей вызова на уровне программы имеет важное значение до любых структурных изменений: то, что кажется простой строковой ссылкой, может быть единственным механизмом, с помощью которого десятки программ достигают критически важной функциональности.
Как SMART TS XL Создает полный реестр звонящих.
SMART TS XL Создается единый граф вызовов для всех языков, платформ и репозиториев в индексированной среде. Программы COBOL, потоки заданий JCL, службы Java, приложения .NET, хранимые процедуры SQL, скрипты Python и другие исходные артефакты анализируются с использованием языкового анализа для создания общего графа перекрестных ссылок. Каждая функция, абзац, процедура, метод и программный модуль являются узлами этого графа. Каждая связь вызова, будь то оператор COBOL CALL, вызов метода Java, JCL EXEC PGM или SQL EXEC, представляет собой типизированное ребро. Граф представляет собой полную топологию вызовов системы, а не частичное представление для каждого языка.
Когда функция помечена для удаления, SMART TS XLФункция перечисления вызывающих абонентов обходит входящий граф вызовов от узла целевой функции, собирая всех вызывающих абонентов на каждом уровне иерархии вызовов. Обход является рекурсивным, следуя по графу через функции-обертки, фасадные слои и промежуточные утилиты, пока не достигнет функций без вызывающих абонентов, которые представляют собой истинные точки входа в цепочки вызовов. Результаты организованы по языку, репозиторию, типу вызывающего абонента и глубине вызова, что дает команде структурированный список, который отделяет прямые вызовы от косвенных, а также активные вызовы от вызовов мертвого кода.
Возможности платформы по анализу влияния изменений позволяют создавать структурированные отчеты о влиянии изменений: в них указывается не только, какие функции вызывают устаревшую функцию, но и какие программы, сервисы, пакетные задания и процедуры JCL затронуты на каждом уровне цепочки зависимостей. Этот отчет является тем самым документом, который делает процесс устаревания и удаления действенным: в нем указываются ответственные лица, определяются конкретные места вызовов и количественно оценивается объем миграции, необходимый для безопасного удаления. Как подробно рассматривалось в разделе « Анализ влияния изменений в управлении изменениями на предприятии» , возможность перечислить затронутые компоненты до внесения структурных изменений является основополагающим требованием для безопасной работы сложных, взаимосвязанных корпоративных систем.
SMART TS XL Также поддерживается этап непрерывного мониторинга процесса устаревания. Поскольку граф перекрестных ссылок постоянно обновляется по мере индексации изменений исходного кода, количество вызовов для устаревшей функции всегда актуально. Интеграция с конвейером CI позволяет автоматическим проверкам завершаться с ошибкой при новых вызовах устаревших функций, обеспечивая соблюдение дисциплины миграции в момент внедрения нового кода, а не обнаруживая нарушения постфактум. Это сочетание первоначального перечисления, рекомендаций по миграции и непрерывного мониторинга охватывает весь жизненный цикл устаревшей функции от аннотирования до безопасного удаления.
Удаление функции без сожаления
Разница между плавным удалением функции и удалением, вызывающим сбои в производственной среде, почти всегда заключается в полноте обнаружения вызывающих функций. Само удаление тривиально: удалить определение и развернуть. Основная работа заключается в подготовке, и качество подготовки зависит от качества инвентаризации вызывающих функций, на которой она основана.
В системах, где граф вызовов неглубокий, одноязычный и находится в пределах одного репозитория, иерархия вызовов IDE и предупреждения компилятора являются достаточной подготовкой. В системах, где граф вызовов охватывает множество языков, множество репозиториев, множество платформ и потенциально несколько десятилетий кода, эти инструменты охватывают лишь небольшую и неизвестную часть фактической поверхности вызова. Именно разрыв между тем, что они возвращают, и тем, что фактически вызывает функцию, является источником сбоев в производственной среде.
Специально разработанная кросс-языковая и кросс-репозиторная система перечисления вызывающих функций не является усовершенствованием рабочего процесса разработчика по удалению функций. Это необходимое условие для безопасного выполнения этого рабочего процесса в любой достаточно сложной системе, в которой накопились те самые межсистемные связи вызовов, которые обычно присутствуют в устаревших функциях в корпоративных кодовых базах. Каждая удаленная устаревшая функция без полного перечня вызывающих функций — это релиз, содержащий неизвестное количество сбоев во время выполнения, ожидающих конкретного пути выполнения, который приводит к отсутствующему определению. Устранение этой неизвестности — вот для чего предназначено структурированное обнаружение вызывающих функций.