The Methods Nobody Calls

by Oscar Poveda, on Sep 1, 2026, 6:38:39 PM

How to Catch the Code That Goes Missing After a Modernization

How to verify whether a code migration is complete

A successful build proves that the modernized code compiles. It doesn't prove that the whole application survived.

A method can exist in the new repository but never be called. A report can render without throwing an error while quietly omitting an entire section. An event handler can compile perfectly after the wiring that once triggered it disappears.

Those aren't syntax errors. They're modernization gaps, and they're much harder to catch.

This is the part of modernization nobody puts on a slide because it's not glamorous. It's plumbing. But verifying that nothing silently dropped out between the legacy application and the modernized one is where a migration quietly succeeds or quietly fails. You need a system built to find what a clean build can't.

Dead code is a signal, not a verdict

When a method was reachable in the legacy application but becomes unreachable after modernization, something may have broken upstream: a call site wasn't restored, an event binding disappeared, a route between forms was lost, or a dependency fell through the cracks of a refactor.

The important word is may. An unreachable method isn't automatically a defect. Legacy applications accumulate code that nobody's used in years, and a whole-codebase translator may carry some of it forward. The useful comparison isn't simply whether the method exists in the new code. It's whether it was reachable before and is no longer reachable now.

VELO's QA agent uses that distinction as a starting point. Code that was already unreferenced in the original application shouldn't be treated as a modernization miss. But when a method participated in the original application's behavior and nothing can reach it in the modernized system, the agent raises an alert for investigation.

We've seen this pattern surface in reports that appear to run successfully but render only part of the expected output. The methods still exist. They compile. They're simply no longer called from the workflow that needs them.

Legacy applications rarely call everything by name

A naive dead-code check will drown a team in false alarms because legacy applications are full of indirect invocation. VB6 code may use late binding or CallByName. Event handlers may be connected through form metadata. COM interfaces may be invoked by external consumers. Modern .NET applications add their own complications: reflection, dependency injection, framework conventions, generated routes, and public APIs with no internal caller.

All of those can make healthy code appear dead to a simple static call graph. A useful QA layer must recognize these invocation patterns as a class, trace them where possible, and clearly label what it can't prove. Otherwise, the signal disappears under a mountain of technically correct but practically useless warnings.

Static analysis cannot prove the whole application survived

Even good static analysis has blind spots. A false positive is an alert that fires on code that turns out to be fine. A false negative is more dangerous: a real behavioral gap that the QA agent never flags.

That's why code analysis needs a second line of evidence. Run the legacy and modern applications against the same inputs, exercise the same workflows, and compare the outputs. If the legacy report contains twelve sections and the modern report contains eleven, a clean build and a clean call graph don't get the last word.

Differential testing is especially valuable when behavior depends on runtime configuration, external integrations, UI metadata, or data conditions that static analysis cannot fully reconstruct. The QA layer should combine both approaches: inspect the code for suspicious structural gaps, then validate behavior where code alone can't settle the question.

It also matters that the QA layer is independent from the translation process. The same agent that generated the code shouldn't be the only agent deciding whether that code's correct. The auditor needs a different job: challenge the output, document uncertainty, and show engineers where additional evidence is required.

Fidelity isn't one magic number

A post-modernization QA pass should replace "does this look right?" with evidence. But one score can't explain every kind of evidence. VELO evaluates three related questions:

  • Architecture conformance: Does the implementation match the target architecture the client requested?
  • Behavioral fidelity: Does the modernized application preserve the business behavior of the original?
  • Verification coverage: How much of that behavior could the QA system confidently evaluate using the available evidence?

The distinction matters most when the target is heavily re-architected. In one project, the client requested an architecture designed around its own infrastructure rather than a like-for-like structural port. The result matched the requested architecture, but the original and modernized implementations no longer lined up file for file or method for method.

A raw semantic score in that situation can be misleading. A low number doesn't necessarily mean the behavior is wrong. It may mean the automated analysis could verify only part of the behavior through structural comparison. The right interpretation isn't "the application is 40% correct." It's "the system could automatically verify roughly 40% using this method, and the remainder needs different tests or human review."

On applications where the original and target structures remain more comparable, automated fidelity scores can provide a stronger measure of parity. But the score's still only useful when it comes with evidence: the affected file, the relevant lines, the rule that fired, and concrete options for resolving the gap. A percentage without traceability is just a more sophisticated-looking guess.

Not every alert's a real problem

A QA system that never questions itself eventually becomes noise, and noise gets ignored.

A method may look unreferenced because a framework invokes it by convention. A dependency-injection entry point may have no visible constructor call. An event handler may be connected through generated metadata rather than source code. A public API may be healthy even though nothing inside the repository calls it.

None of that means "ignore the alert." It means the alert needs context and the reviewer needs judgment. It also means the rules should improve. Every confirmed false positive becomes an opportunity to refine the detection logic so the same harmless pattern does not generate the same alarm on the next project.

False negatives need a different response: differential tests, regression suites, telemetry, and targeted human review. Static analysis and behavioral testing cover each other's blind spots.

Topics:AICode MigrationAgentic AIagentic modernization

Comments

Subscribe to GAPVelocity AI Modernization Blog

FREE CODE ASSESSMENT TOOL