Skip to content
Aug 14, 2026·7 min read

A legacy code audit worth paying for

A legacy code audit should map modules, measure dead code, rank business risks, and price each migration phase. Use this standard before you pay.

A legacy code audit worth paying for

A legacy code audit is worth paying for only when another competent team can act on it without buying a second discovery project. It must explain what exists, what still runs, which business processes can fail, and what each sensible unit of change will cost. If the document stops at architecture diagrams, code-quality scores, and a recommendation to modernize, you bought a sales document.

I have reviewed audits that looked expensive because they were long. The useful ones were usually harder to produce and easier to read. Every claim led back to code, runtime evidence, an owner interview, or an explicit assumption. Every proposed phase had a boundary, an acceptance test, dependencies, and a price. That level of traceability is the dividing line.

The audit must define the system boundary before judging it

A credible audit starts by stating exactly what was inspected and what was not. Legacy systems rarely fit inside one repository. A COBOL application may depend on JCL, scheduler definitions, copybooks, DB2 objects, file transfers, and an Excel workbook maintained by finance. A desktop application may call a shared database directly while a nightly script fixes records that the user interface cannot. Leaving those edges out makes every later conclusion unreliable.

The scope register should identify repositories, branches, deployed versions, build files, database schemas, job definitions, interfaces, configuration stores, reports, and operator procedures. For each item, the auditor should record the source of evidence and the version or observation date. A repository name without a commit identifier does not establish what was analyzed. A database schema exported from development does not prove what production uses.

The audit also needs an exclusion list. Perhaps the vendor package cannot be inspected, production logs retain only seven days, or the team cannot run the old compiler. Those are not footnotes. They limit what the auditor can claim. Each exclusion should state its consequence, such as "batch reachability remains uncertain because scheduler history was unavailable."

Ask for a coverage table with four columns: asset, evidence inspected, confidence, and gap. Confidence should reflect evidence quality, not the auditor's mood. Code plus build output plus production traces earns more confidence than an interview alone. If the report gives strong conclusions without showing its boundary and gaps, the analysis cannot be reproduced and the price is hard to defend.

A module map has to show behavior, data, and ownership

A useful module map is an inventory joined to dependency evidence, not a page of colored boxes. It lets an engineer select a module and answer four questions: what calls it, what it calls, which data it reads or changes, and which business process depends on it. The map can be a graph, a table, or both. The format matters less than stable identifiers and traceable edges.

At minimum, each node should carry a repository path, language, deployable or job, entry points, persistent data touched, external interfaces, execution schedule or trigger, and known owner. Each edge should say why the auditor believes the dependency exists. Static call resolution, import analysis, SQL parsing, scheduler configuration, message metadata, and observed traffic are different kinds of evidence. Combining them without labels hides uncertainty.

Legacy dependency analysis is messy. Dynamic calls, generated names, reflection, shared tables, temporary files, and control records can defeat a simple parser. The report should preserve unresolved edges instead of quietly dropping them. A node marked "target computed at runtime" is useful; a clean diagram that omits the call is dangerous. Cycles should remain visible because they often determine extraction order.

The map also needs a business overlay. "Program ARUPD07 calls subroutine DATECNV" helps an engineer. "The cash-application close depends on ARUPD07 and the bank-file import" helps decide what can move safely. Both views belong in the same model, with evidence connecting them. If the audit relies on a workshop to recreate that relationship after delivery, the deliverable is incomplete.

Request the underlying data, not only rendered diagrams. A simple edge file is enough to make the work inspectable:

source_id,target_id,edge_type,evidence,confidence,business_process
ARUPD07,DATECNV,static_call,src/ar/ARUPD07.cbl:418,high,cash_application
NIGHTLY_AR,ARUPD07,schedule,ops/sched/nightly.jcl:77,high,cash_application
ARUPD07,BANK_RATE,dynamic_call,production_trace:sample_042,medium,cash_application

A team can diff that file, query it, and load it into whatever graph tool it prefers. A screenshot locked inside a PDF cannot support planning or later verification.

Dead code is a measured claim, not a percentage from a scanner

The audit should separate unreachable code, unobserved code, dormant code, and unused data structures because they support different decisions. Teams routinely blur these categories and then delete something that runs only at year-end. Static reachability can show that no known entry point reaches a procedure. Runtime observation can show only that a procedure did not execute during the observation window. Those statements are not equivalent.

A dead-code register should identify the unit, the method used, the observation window, relevant business cycles covered, contrary evidence, confidence, and recommended action. A line count by itself is weak. Ten thousand generated lines may be safe to regenerate, while a twenty-line tax exception can carry serious business risk. Measure by logical unit and behavior before rolling anything into a percentage.

The auditor should use several evidence types where the platform permits it: build references, static reachability, scheduler history, production traces, database access records, feature flags, and interviews with operators. None is complete alone. Job history can miss manually triggered recovery work. Production traces can miss quarter-end paths. Interviews can preserve folklore long after a feature stopped running. Agreement among independent sources raises confidence. Disagreement belongs in the report.

A defensible record might read: "Module CLAIMS_REPRINT has no static callers, no scheduler entry, and no observed execution across two normal billing cycles. Operations reports that support invokes it manually after printer failures. Classification: dormant recovery path, not dead. Action: retain until the replacement includes the recovery procedure." That paragraph is more useful than a dashboard claiming 18 percent dead code.

Demand the numerator and denominator behind every summary number. Does "unused" mean lines, functions, programs, tables, screens, or batch steps? Were comments and generated sources excluded? Did the scan cover conditional compilation and dynamically resolved calls? If the audit cannot answer those questions, treat its dead-code figure as a lead for investigation, not a basis for scope or savings.

Risks must point to business processes and failure modes

A ranked risk list earns its place only when each item connects a technical condition to a business event, a failure mode, and observable impact. "High coupling" is not yet a risk. "The invoice-posting job and credit-limit service update the same balance table through separate rules; partial failure can release orders against a stale balance" is a risk that leadership can evaluate.

Each risk entry should include the affected business process, initiating event, technical cause, failure behavior, detection method, existing control, likely scope of impact, evidence, and remediation option. The ranking should show how likelihood and impact were assigned. Exact-looking numbers do not make subjective inputs objective, so simple scales with written definitions often work better than decimal scores.

Do not let the auditor rank maintainability findings above production behavior merely because static analyzers make them easy to count. A 4,000-line procedure may be unpleasant but stable, well-contained, and rarely changed. A tidy 200-line reconciliation routine may silently drop records when a file arrives twice. Rank the second issue higher if evidence supports it. Business exposure, change frequency, recoverability, and detection delay matter more than aesthetic offense.

Risks also need owners. The owner is usually accountable for resolving or accepting the business exposure, not necessarily the developer who knows the module. If nobody can own a risk because the affected process has no clear owner, record that governance gap rather than assigning it to "IT."

I use one blunt test in review: can the person who runs the process recognize the described failure? If accounts receivable, warehouse operations, or compliance staff cannot connect the entry to an event they handle, the auditor has probably ranked code smells rather than operational risks.

Runtime evidence has to cover the calendar that matters

Price the actual rewrite
The quote starts with your codebase and target, not a generic modernization stage diagram.

A production trace is useful only when its observation window matches the business calendar. Thirty ordinary days may cover thousands of web requests while missing quarter-end reporting, annual renewals, seasonal pricing, or a recovery job used after a rare upstream failure. The audit should identify which cycles the evidence covers and which it does not.

Start with a process calendar: daily, weekly, month-end, quarter-end, annual, event-driven, and recovery-only work. Then map execution evidence to it. This prevents a high-volume online path from drowning out low-frequency batch behavior. The process calendar should come from scheduler configuration, operating procedures, transaction records, and owner interviews, not memory alone.

Privacy and operational limits belong in the method. The auditor may need to work with request shapes, hashes, counts, sampled payloads, or redacted logs rather than raw production data. The report should state how sensitive fields were handled and what fidelity was lost. It should never copy credentials or personal data into a deliverable merely to prove that tracing occurred.

The deliverable should show enough raw shape to test the conclusions: trace identifiers, timestamps, entry points, exit status, called components, tables or files touched, and whether the event joined a named business process. Aggregates should retain links to the observations beneath them. Without that chain, an auditor can label a path "hot" or "unused" without showing why.

Missing telemetry does not justify pretending the code is dead. It changes the recommendation. The phase plan may include a short instrumentation step, an extended observation window, or a controlled replay before any deletion. Uncertainty is normal in an old system. Concealing it is not.

The phased plan must have boundaries, proof, and prices

A phase is purchasable only when its scope, dependencies, acceptance evidence, and price are explicit. Labels such as "foundation, transformation, optimization" reveal nothing about what will be different when the invoice arrives. A good phase names the business slice or technical seam, the assets changed, the interfaces held stable, and the proof required to accept the work.

Every phase should state five things:

  1. The included modules, data, interfaces, and business process.
  2. Preconditions and decisions the customer must supply.
  3. Deliverables and the environment in which they will run.
  4. Acceptance tests, including behavior parity and operational checks.
  5. Fixed price or a bounded price range with assumptions that move it.

Prices without assumptions are bait. Assumptions might cover access to build tools, availability of representative traffic, the condition of database definitions, licensing constraints, or the customer's responsibility for user acceptance. Each assumption should point to a priced change mechanism. If a missing interface definition adds work, the report should say how the phase will be rescoped or repriced, not reserve a general right to bill more.

The sequence needs an argument. Teams often start with a small, isolated module because it appears safe. That can prove syntax conversion while teaching nothing about the dependencies that make the system hard. A better first phase often crosses one representative seam, exercises the data path, build pipeline, deployment model, and parity method, while limiting business exposure. The audit should explain why its first phase reduces the largest planning uncertainty.

Alternatives belong in the plan when the evidence supports more than one route. For example, the team might extract a pricing service first or stabilize the shared database contract before extraction. Show the cost, dependency, and risk tradeoff. A single prescribed roadmap can hide the auditor's delivery preference behind a claim of technical necessity.

Estimates should be reconstructable from the evidence

Preserve behavior, change architecture
CodeHero modernizes the design while a parity harness checks behavior against recorded traffic.

A trustworthy estimate lets another experienced team see how scope became price. It does not need to expose an auditor's payroll or margin, but it must expose units, complexity drivers, exclusions, contingencies, and assumptions. Otherwise the number is an invitation to negotiate, not a planning instrument.

The estimate model should connect to the module and risk registers. A phase that includes twelve programs, three batch jobs, two external interfaces, and one shared table should reference those exact identifiers. Complexity adjustments should name the condition: dynamic dispatch, missing build automation, undocumented file formats, data conversion, or an unavailable test environment. "Legacy complexity multiplier" is too vague to audit.

Ranges are reasonable when evidence is incomplete, but the report must explain what collapses the range. If the lower price assumes a reproducible build and the upper price assumes reconstructing it, a one-day build test may turn uncertainty into a firm quote. That is useful discovery. A wide range with no decision rule merely transfers estimating risk to the buyer.

Ask the auditor to provide a phase ledger you can inspect:

Phase: cash application slice
Scope IDs: NIGHTLY_AR, ARUPD07, DATECNV, BANK_RATE
Base work: behavior capture, target implementation, data adapter, deployment
Risk allowances: dynamic call resolution; incomplete printer-recovery trace
Customer inputs: redacted traffic set; operations reviewer
Acceptance: replay parity; close totals match; recovery procedure demonstrated
Price: [amount or bounded range]
Range closes when: build and recovery-path tests complete

The exact estimating method can vary. The chain from discovered asset to work unit to price cannot. If the auditor refuses to show that chain because estimation is proprietary, you cannot tell whether the price reflects your system or a sales target.

A real audit leaves evidence your team can challenge

The final package should include editable registers and machine-readable exports alongside the narrative. Your engineers should be able to filter all high-confidence dormant modules, trace a risk to its affected tables, or see which phase owns an interface. If the auditor controls the only working model, you have rented understanding rather than purchased it.

Require evidence identifiers throughout the report. A module edge can cite a source location or trace. A risk can cite module edges, incidents supplied by the customer, and an owner interview. A phase can cite the risks it reduces and the assets it changes. That chain makes review concrete. Engineers can dispute an edge or an assumption instead of arguing about a consultant's conclusion.

The package should also contain reproduction notes: tools and versions, analysis commands or settings, branch and commit identifiers, trace windows, filters, parser limitations, and manual corrections. The goal is not perfect automation. The goal is to distinguish generated evidence from human judgment and make both inspectable.

Set a review session where the auditor has to answer from the delivered artifacts. Pick one revenue path, one batch path, one supposedly dead module, and one expensive phase. Follow each backward. If the trail breaks, record the missing artifact before accepting the audit. This review is much more revealing than asking for another presentation.

Ownership after delivery matters too. The contract should state that the customer receives the registers, schemas, diagrams, scripts created specifically for the audit, and usable exports from any proprietary analysis environment. Tool licensing may limit what can transfer, but your system model should not disappear when access expires.

Sales documents reveal themselves before the final pitch

Include the awkward edges
Mixed-language trees are read in parallel, including jobs and database code around the main application.

A sales document starts with a predetermined destination and collects enough evidence to justify it. A real audit allows evidence to change the recommendation, including the possibility that part of the system should remain in place. You can often tell which one you are buying from the proposal and the first evidence review.

Watch for these signs:

  • The deliverables promise findings and recommendations but do not define artifact fields or evidence sources.
  • The audit price is low because discovery cost will be recovered through an assumed implementation.
  • Risk scores come from a generic scanner and have no business-process mapping.
  • The roadmap uses broad stages without acceptance tests or phase prices.
  • The vendor will show its model but will not deliver the underlying records.

Also reject theater disguised as precision. A maturity score of 2.7, a heat map full of red cells, or an exact modernization percentage can look analytical while hiding arbitrary weights. Ask what decision changes if the score moves. If nobody can answer, the score decorates the pitch.

Independence is not the same as neutrality. An implementation firm can produce a good audit if it separates evidence from recommendation, prices alternatives, and transfers the artifacts. A consultancy with no delivery capability can still write a vague report. Judge the work product and commercial incentives, not the label on the firm.

Before signing, put acceptance language in the statement of work. Name the required registers, minimum fields, export formats, traceability links, review procedure, and correction period. Do not accept "comprehensive report" as a deliverable. Comprehensive is an adjective; a module register with defined columns is something you can inspect.

The audit should make the first delivery decision possible

The audit is finished when leadership can choose a bounded first phase, understand the risk it reduces, see the systems and people it touches, and approve a price tied to evidence. A thick report that ends with "conduct further discovery" has not crossed that line. Some unknowns will remain, but each one should have an owner, a way to resolve it, and a decision it affects.

Use a final decision record with the chosen phase, rejected alternatives, evidence references, unresolved assumptions, acceptance tests, price, and stop conditions. A stop condition might be failure to reproduce the current build, traffic that contradicts the module map, or discovery that an external vendor controls a required interface. Naming those conditions protects both buyer and delivery team from pretending the estimate survived a changed premise.

If the goal is a rewrite, the parity method belongs in this decision. CodeHero reads the complete codebase and verifies preserved behavior against recorded production traffic, but the same buying standard applies to any vendor: make them define what behavior they will compare, which traffic represents it, how differences are classified, and who accepts them. "Functionally equivalent" without a harness and an adjudication process is another adjective.

Pay for an audit when it converts uncertainty into traceable choices. Refuse it when it converts uncertainty into slides. The practical test is simple: hand the artifacts to an engineer who missed every workshop and ask for the scope, evidence, risk, acceptance test, and price of phase one. If those answers are present and linked, the audit has done its job.

FAQ

How much should a legacy code audit cost?

The price should follow the inspected assets, evidence gaps, environments, and required deliverables. Reject a flat number that does not show scope units and assumptions; you need to see how repositories, interfaces, runtime tracing, and business-process interviews contribute to the work.

How long should a legacy system audit take?

Duration depends on access, codebase size, build reproducibility, runtime evidence, and the business calendar that must be observed. Ask for milestones tied to artifact completion, not elapsed time alone, and require the auditor to identify any cycles that the observation window misses.

Can static analysis identify all dead code?

No. Static analysis can identify unreachable paths under its model, but dynamic calls, scheduler entries, manual recovery jobs, and rare business cycles can escape that model. Combine static results with runtime and operational evidence before deleting anything.

What should a legacy application module map contain?

It should identify modules, entry points, calls, data access, interfaces, triggers, deployables, owners, and business processes. Every dependency edge needs an evidence type and confidence level, and the buyer should receive the underlying data rather than only a diagram.

Should the company doing the audit also perform the rewrite?

It can, provided the audit transfers its evidence, prices alternatives, and does not assume a rewrite regardless of findings. Separate audit acceptance from the implementation sale so weak artifacts cannot pass merely because the next contract is waiting.

How do you rank risks in legacy code?

Tie each technical condition to a business event, failure mode, detection method, existing control, and scope of impact. Rank business exposure, recoverability, change frequency, and evidence quality instead of treating code-quality scores as operational risk.

What evidence proves that code is unused?

No single source always proves it. Strong evidence combines static reachability, build references, scheduler history, production traces, data access, feature configuration, and operator knowledge across the relevant business cycles.

What should be priced in each modernization phase?

Price the named modules, interfaces, data work, behavior capture, implementation, deployment, and acceptance testing included in that phase. Show assumptions, exclusions, risk allowances, customer inputs, and the rule for repricing when an assumption fails.

How can a buyer validate an audit before accepting it?

Choose representative online, batch, dormant, and high-risk paths and trace each conclusion back through the delivered evidence. Confirm that a separate engineer can reconstruct the phase scope and estimate without relying on workshop memory or the auditor's private tool.

What is the clearest sign that an audit is a sales document?

Its recommendations are specific while its evidence and deliverables stay vague. If the vendor can name the destination but cannot provide editable registers, traceable risk entries, acceptance tests, and phase prices, the sales decision came before the analysis.