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

Мертвый код - это не любой старый, неудобный или редко работающий код. Так можно назвать код, о достижимости, выполнении и бизнес-владельце которого есть конкретное утверждение с доказательствами. Эти утверждения отличаются друг от друга. Если свести их к одной метке, при очистке или переписывании легко удалить единственную реализацию годовой корректировки, спящего договора или аварийного восстановления.
Я видел, как команды удаляли процедуры, названия которых никто не узнавал, а потом выясняли, что планировщик запускал их от служебной учетной записи в последний рабочий день года. Входящих вызовов из приложения не было, обычная выборка трафика не показывала выполнение, но один настоящий вызывающий процесс существовал. Нормальный метод измерения должен выдерживать такую проверку.
Считайте предполагаемый мертвый код задачей о доказательствах. Статический анализ показывает, что может быть вызвано. Покрытие во время работы фиксирует, что вызывалось при заданных условиях. Каталоги заданий, календари, конфигурация, значения данных, инструкции операторов и архивы трафика обнаруживают входы, которых оба метода по отдельности не видят. Удалять можно, когда записи согласуются, владелец принимает вывод, а тест на паритет не находит потери поведения.
У мертвого кода три разных значения
Отчет должен разделять невозможные пути, ненаблюдавшиеся пути и устаревшее поведение. Для каждой категории нужна своя мера. Один общий процент скрывает неопределенность, которую должен видеть проверяющий.
Статически недостижимый код не имеет допустимого пути ни от одной заявленной точки входа в модели анализатора. Сюда может попасть закрытая функция без ссылок внутри изолированного модуля или ветка с условием, ложность которого доказывает система типов. Это самый сильный технический сигнал, но его полнота зависит от списка входов и модели диспетчеризации.
Код, не наблюдавшийся во время работы, не получил ни одного попадания в названном окне и наборе нагрузки. Это ничего не говорит о других датах, клиентах, ролях, диапазонах данных, сбоях и действиях операторов. Обычный веб-трафик часто не охватывает пакетные задания, восстановление, административные экраны, миграции и ветку для отрицательного остатка после сторнирования. Называйте такой код ненаблюдавшимся, а не недостижимым.
Функционально устаревший код все еще выполняется или остается достижимым, но компании больше не нужен его результат. Налоговое правило для страны, откуда ушел бизнес, может устареть. Расчет для закрытого продукта может требоваться для исправления старых счетов. Решение принимает бизнес-владелец с учетом договоров и хранения данных. Компилятор этого не знает.
Используйте поле состояния вместо флага да или нет:
- недостижим в модели M
- не наблюдался в нагрузке W за окно T
- сохранен для редкого события E
- устарел по решению D
- неизвестно, требуется расследование
Такой словарь предотвращает обычную подмену. Инженеры начинают с нулевого числа попаданий, на встрече называют код мертвым и одобряют удаление так, будто доказали невозможность вызова. Слова изменились, доказательства остались прежними.
Статическая достижимость доказывает невозможность, а не ненужность
Статический анализ лучше всего доказывает, что обычный поток управления не достигает символа от известных корней. Хуже всего он работает там, где старая система превращает имена и данные в управление. Результат должен хранить корни, правила разрешения и слепые зоны.
Сначала перечислите все допустимые точки входа, а не только главный исполняемый файл. Для мейнфрейма это транзакционные программы, пакетные шаги JCL, вызываемые модули, выходы, триггеры базы и утилиты операторов. На AS/400 программы CL и описания заданий могут вызывать RPG без интерактивного пути. В настольных системах есть входы COM, макросы отчетов и ассоциации файлов. В веб-монолитах есть запланированные скрипты, обработчики очередей, hooks фреймворка и маршруты из конфигурации.
Затем постройте граф вызовов с оценкой уверенности для каждого ребра. Прямой вызов, разрешенный компилятором, надежнее строки, похожей на имя процедуры. Не скрывайте неразрешенные косвенные вызовы. Указатели на функции, reflection, внедрение зависимостей, позднее связывание, созданный SQL, имена хранимых процедур и реестры плагинов создают ребра, которых не найдет простой поиск текста.
Удаление секций линкером и компилятором отвечает на более узкий вопрос. В руководствах описано удаление инструкций, которые не влияют на программу при конкретной сборке. Это уменьшает бинарный файл, но не доказывает безопасность удаления исходной процедуры для других флагов, отдельно загружаемых модулей, скриптов и операционных входов. Оптимизированный бинарный файл не описывает все приложение.
Небольшая проверяемая таблица полезнее цветного графа на десять тысяч узлов. Экспортируйте по строке на кандидата: символ, файл, проверенные корни, прямые входящие вызовы, возможные косвенные ссылки, варианты сборки и версию анализатора. Если ребро зависит от строки, ключа конфигурации, записи базы или имени задания, храните это доказательство рядом.
Сильный статический вывод требует четырех ответов: какие корни вошли, какие языки и созданные артефакты разобраны, как разрешались косвенные вызовы и какие компоненты находятся вне репозитория? Если ответ неизвестен, вывод остается предварительным.
Покрытие доказывает выполнение, а не безопасность
Покрытие может доказать выполнение кода в записанной нагрузке. Нулевое число попаданий не доказывает ненужность. Покрытие свидетельствует о присутствии, но не доказывает отсутствие.
GNU gcov сообщает, сколько раз выполнялись строки и ветки инструментированной программы. Coverage.py проводит похожее различие для Python и записывает цели переходов. Оба руководства связывают результат с предоставленными запусками. Это условие важнее процента: запуск охватывает только свои входные данные, окружение, даты, учетные записи и сбои.
Покрытие строк теряет детали управления. Строка со сложным условием может выполняться, хотя один операнд не меняется. Оператор switch может выполняться, а один case останется нетронутым. Покрытие веток улучшает доказательства, но не подтверждает правильность результата или побочного эффекта. Перед удалением собирайте ветки или ребра и сравнивайте выходы.
Инструментируйте все поверхности выполнения, найденные статически. Интерактивного трафика редко достаточно. Включите пакетные программы, workers, расписания, генераторы отчетов, процедуры базы, восстановление и административные инструменты. До объединения храните версию, вход, задание, клиента или подразделение, роль, дату и источник нагрузки. Общая карта скрывает, что строка работала только при годовом закрытии.
Окно наблюдения должно следовать бизнес-календарю, а не удобному числу дней. Охватите ежедневные, недельные и месячные операции, конец квартала и года, продления, истечения, переходы времени, високосный день и настоящую отчетность. Если ждать нельзя, повторите записанную нагрузку или воссоздайте событие в изоляции. Три загруженных будних дня не заменяют финансовый год.
Инструментирование может менять время, память и сбои. Измеряйте расходы рядом с гонками, таймаутами и границами пакетной обработки. Если полное покрытие опасно, применяйте выборочные трассы, счетчики на стабильных границах, аудит базы или журналы заданий. Более слабое доказательство допустимо, если его честно назвать и объединить с другими.
Время входит во входные данные
Редкий календарный код остается рабочим кодом, чьи входные данные включают дату, отсечку или накопленное состояние периода. Команды пропускают его, когда считают время фоном.
Возьмем распределение на конец года. Онлайн-приложение весь год проводит обычные записи. В последний рабочий день планировщик запускает пакет после закрытия главной книги. JCL передает режим, управляющая таблица выбирает счета с отложенным остатком, программа создает выравнивающие проводки в следующем периоде. Веб-запросы процедуру не вызывают. Ее сокращенное имя никто не узнает. Одиннадцать месяцев покрытия показывают ноль.
При переписывании процедуру удаляют и заменяют сервис книги. Обычные проверки проходят, потому что данные относятся к марту и июню. В конце года суммы остаются сбалансированными, поэтому простые бухгалтерские проверки тоже проходят. Дефект проявляется позже, когда выписки расходятся с договорным правилом распределения. Потерянное правило было сочетанием даты, управления заданием, состояния таблицы и периода вывода.
Чтобы найти такой код, составьте перечень событий рядом с графом. Спросите финансы, эксплуатацию, поддержку и специалистов по требованиям, но проверьте также планировщики, JCL, cron, историю, инструкции, управляющие таблицы, календари отчетов, поступление файлов и архивные отметки. Ищите сравнения дат, номера периодов, праздники, особые режимы и старые годы отсечки.
Запишите последнее выполнение и следующую ожидаемую возможность. Процедура, работавшая в прошлое закрытие, имеет понятное объяснение. Процедура без попаданий четыре года может поддерживать пятилетний срок исправлений. Ночное задание может войти в ветку только при наличии управляющей записи. Частота вызывающего процесса не равна частоте каждой его ветки.
Подмена часов заслуживает отдельного тестового интерфейса. Передавайте время через управляемый источник и согласованно фиксируйте часы базы и планировщика. Если один компонент читает симуляцию, а другой часы хоста, тест создает невозможные состояния.
До удаления нужен реестр доказательств
Реестр превращает расплывчатый спор в выводы, которые повторит другой инженер. Он должен сопровождать миграцию, проходить проверку как код и хранить исходные ссылки для каждого решения.
Достаточно строки на символ или цельную функцию. Запишите ID, символ, статическое состояние, корни, неразрешенные ребра, попадания, окно, нагрузки, редкое событие, внешние вызовы, владельца, решение и места хранения доказательств. Не превращайте неизвестно в ноль. Ноль означает измерено и отсутствует; неизвестно означает не измерено.
Хранилище покрытия даст список кандидатов обычным запросом. Измените имена под свою схему, но оставьте группировки:
SELECT s.symbol_id, s.qualified_name,
COALESCE(SUM(c.hit_count), 0) AS hits,
MIN(c.observed_at) AS first_seen,
MAX(c.observed_at) AS last_seen,
COUNT(DISTINCT c.workload_id) AS workloads
FROM symbols s
LEFT JOIN coverage_events c ON c.symbol_id = s.symbol_id
WHERE s.release_id = :release_id
GROUP BY s.symbol_id, s.qualified_name
ORDER BY hits, s.qualified_name;
Вывод должен быть простым: символ, число попаданий, первое и последнее наблюдение, количество нагрузок. Свяжите его с таблицей, где отмечены онлайн-трафик, конец месяца, конец года, восстановление, администрирование и повтор. Ноль в наборе только с онлайн-трафиком не делает пакетный символ кандидатом на удаление.
Добавьте таблицу решения:
| Статический результат | Результат выполнения | Бизнес-доказательство | Действие |
|---|---|---|---|
| Недостижим | Нет попаданий | Нет внешнего входа | Изолировать и проверить удаление |
| Достижим | Нет попаданий | Есть редкое событие | Сохранить и добавить тест |
| Достижим | Есть попадания | Владелец считает устаревшим | Проверить вызовы и вывести из работы |
| Неизвестно | Нет попаданий | Неизвестно | Исследовать, не удалять |
Первая строка все равно требует сборки и поведенческой проверки. Созданный код, варианты сборки и упаковка могут открыть закрытый граф. Третья тоже требует внимания: вызывающие процессы могут зависеть от побочных эффектов. Убирайте путь и обязательства по данным вместе.
Редкие пути нужно запускать специально
Редкому пути нужна целевая нагрузка, а не надежда на будущее покрытие в производстве. Стройте тесты из перечня событий и задавайте ожидаемый результат достаточно точно, чтобы заметить потерянное правило.
Записанный производственный трафик сохраняет сочетания, которых нет в искусственных данных. Очищайте чувствительные поля, сохраняйте порядок при зависимости от состояния и фиксируйте таблицы, часы и файлы. Запрос без состояния базы часто нельзя повторить. Для пакетов архивируйте входы, параметры, коды, файлы, изменения базы и сообщения оператору.
Добавьте синтетические границы: день до события, саму дату и день после; пустой ввод; минимальный счет; сторнирование; поздно открытый счет; перезапуск после частичной обработки. Они отдельно проверяют выбор, расчет, запись и восстановление.
Сравнивайте наблюдаемые границы: HTTP, записи базы, файлы, сообщения, коды выхода, округление, порядок и окна повторов. Нормализуйте переменные ID, но не бизнес-значения. Для записи фиксированной ширины сравнивайте позиции и заполнение, поскольку потребитель может зависеть от байтов.
Не гонитесь за одним процентом. Высокое покрытие может пропустить ветку финансового периода 13. Свяжите каждого кандидата с достигающей его нагрузкой или докажите, почему такой нагрузки нет. Полезная единица - объясненный символ.
Если путь нельзя безопасно выполнить, сделайте характеристический тест ниже него. Вызовите расчет с записанными данными, запустите процедуру в восстановленной базе или пакетный модуль с настоящими параметрами. Точно опишите непроверенную границу.
Удаление должно быть контролируемым экспериментом
Удаляйте небольшими обратимыми частями и дайте системе опровергнуть вывод. Лучший тест убирает кандидата, собирает все варианты, повторяет нагрузки и сравнивает эффекты с исходным состоянием.
При запутанных зависимостях сначала изолируйте код. Поставьте один адаптер, уберите дублирующие входы и добавьте счетчики на границе. Структура меняется без изменения поведения, а точка измерения становится чище. Отсутствие вызовов на всем наборе событий усиливает основание для удаления.
Храните четыре артефакта: строку реестра, точный diff, исходные выходы и выходы после удаления. Запускайте тесты, альтернативные цели, пакеты, администрирование и повтор трафика. Проверяйте изменения базы и файлы, а не только успешное завершение. Нулевой код возврата может сопровождать пропущенную проводку.
Теневое сравнение подходит расчету без побочных эффектов. Запустите старую и новую логику на одном входе, запретите записи с одной стороны и сравните результат. Не дублируйте списания, уведомления, резервы и общее состояние без безопасного приемника.
Совет удалять все с нулевым покрытием после фиксированного срока популярен из-за простой метрики. Он неверен для календарных событий, спящих договоров, ручного восстановления и диспетчеризации по данным. Задавайте требования по классу: онлайн-проверке нужны разные роли и клиенты, закрытию нужна точная нагрузка закрытия.
Сохраняйте настоящий откат. Revert исходников не поможет, если удаление поменяло данные, убрало столбец, остановило файл или изменило контракт сообщения. Отложите разрушительные изменения схемы и храните совместимость до получения доказательств от потребителей.
При переписывании сначала сохраняют поведение
До выбора новой реализации нужно классифицировать и проверить старое поведение. Перенос каждой достижимой процедуры сохраняет случайную структуру, а удаление каждой подозрительной теряет правила. Сначала нужен паритет обязательств, потом изменение архитектуры.
Стройте проверочный стенд на наблюдениях производства и редких событиях. Дайте обеим системам одинаковые упорядоченные входы и исходное состояние. Сравните ответы, постоянное состояние, файлы, сообщения и сбои. При новой форме интерфейса сравнивайте каноническое бизнес-представление, а не копируйте внутренние модули.
Здесь важна разница между паритетом кода и поведения. Новому сервису Go не нужны абзацы COBOL и глобальные переменные формы VB6. Ему нужны те же суммы, решения о допустимости, округление и восстановление, пока владелец не одобрит изменение. CodeHero использует записанный производственный трафик в проверке паритета и одновременно обновляет архитектуру, для этой задачи сравнивать нужно именно на таком уровне.
Для удаленного поведения создайте отрицательную проверку. Если старый отчет должен исчезнуть, проверьте, что задание не планируется и файл не выходит. Если правило перестает действовать, добавьте ранее подходящую запись и проверьте новое одобренное решение. Отсутствие проверяется на заданной границе.
Не теряйте происхождение решений. Свяжите каждое реализованное, измененное или пропущенное правило с реестром. Проверяющий должен найти нагрузку, владельца и эффект для каждой ветки, а также доказательства исчезновения исходной процедуры.
Установите проверяемое правило удаления
Политика работает, если задает доказательства, полномочия и условие остановки. Напишите ее так, чтобы проверяющий мог отклонить изменение без спора о возрасте кода.
Защитимый барьер может требовать:
- В полном перечне входов нет необъясненного входящего пути, либо все оставшиеся вызовы тоже выводятся из работы.
- Данные выполнения охватывают нужные нагрузки и календарные события, а исходные записи сохранены.
- Проверены внешние вызовы, конфигурация, планировщик, диспетчеризация по данным и инструкции операторов.
- Владелец одобрил устаревание, либо технические доказательства показали невозможность без бизнес-решения.
- Удаление прошло сборки, целевые тесты, повтор трафика и сравнение эффектов, а для данных и контрактов есть откат.
Барьер должен разрешать результат неизвестно. Некоторые процедуры нельзя классифицировать до восстановления архива или воссоздания закрытия. Такая метка точно называет недостающее доказательство. Удаление ради улучшения процента ничего не улучшает.
Считайте исследованных кандидатов, доказанно недостижимые пути, редкие пути с тестами, одобренное устаревшее поведение, неизвестные случаи и удаления с пройденным паритетом. Не награждайте число удаленных строк. Правило конца года из десяти строк может нести больше обязательств, чем десять тысяч строк заброшенных экранов.
Возьмите одну якобы мертвую процедуру и запишите точное утверждение. Если там сказано лишь, что никто не видел ее работу, вы измерили знакомство команды с кодом. Добавьте корни, нагрузки, события, владельцев и эффекты, пока другой инженер не сможет повторить вывод. После этого удаление становится частью процесса.
Вопросы
Чем мертвый код отличается от неиспользуемого?
Для мертвого кода доказано, что обязательное выполнение его не достигает. Неиспользуемым часто называют код, которого не увидел инструмент или период наблюдения, хотя внешние вызовы и редкие события могут существовать.
Может ли статический анализ доказать, что код мертв?
Он доказывает недостижимость внутри заданной модели. Reflection, созданные вызовы, конфигурация, планировщики, триггеры и внешние компоненты ограничивают доказательство.
Можно ли безопасно удалить функцию с нулевым покрытием?
Нет. Ноль означает лишь отсутствие выполнения в измеренных нагрузках и датах. Нужны также статическая достижимость, редкие события, внешние вызовы и бизнес-решение.
Как долго нужно собирать покрытие?
Следуйте бизнес-календарю, а не фиксированному сроку. Окно или повтор должны включать нужные задания, закрытия, продления, истечения, восстановление и годовую обработку.
Как найти код, который работает только в конце года?
Проверьте историю планировщика, JCL, задания, инструкции, управляющие таблицы, архивы и условия дат. Воссоздайте закрытие с настоящими параметрами и состоянием, затем измерьте ветки и выходы.
Считается ли удаленный компилятором код мертвым исходным кодом?
Не всегда. Компилятор доказывает, что может пропустить одна сборка. Другие флаги, модули, скрипты и точки входа могут требовать исходник.
Что хранить в реестре доказательств?
Запишите символ, входы, неразрешенные ребра, нагрузки, даты, события, внешние ссылки, владельца, решение и исходные доказательства. Не смешивайте неизвестно и ноль.
Как проверить удаление старого кода?
Зафиксируйте исходные выходы, удалите небольшого кандидата, соберите варианты и повторите нагрузки. Сравните состояние, файлы, сообщения, коды и сбои, а не только ответы.
Достаточно ли высокого покрытия при переписывании?
Нет. Высокий общий процент может пропустить финансовую ветку или восстановление. Свяжите поведение с нагрузками и сравните старые и новые эффекты.
Кто должен одобрить удаление старого бизнес-правила?
Названный бизнес-владелец должен подтвердить окончание обязательства с опорой на технические доказательства. Инженеры могут доказать невозможность пути, но не должны выводить изменение договора из тишины.