Most advice for understanding a legacy codebase assumes you can run it: set a breakpoint, step through, watch it work. In equipment software you usually can't, because the machine is on a customer's floor and the hardware is booked. That inverts the workflow. Reading the code becomes the primary instrument, which leaves symbol resolution, find-references and call hierarchy doing the work a debugger usually does.
The machine is somewhere else
When you’re developing for manufacturing and fabrication, the code is on your PC, but the machine it controls is in a factory in another country. To add salt to the wound, the engineer who first wrote the module you’re now working on left two years ago. You’re left with zero context comments, no tome of documentation to fall back on, and commit messages from a bygone era saying things like “fix.”
That happens on a regular basis as devs get moved around or, in the worst case, retire. And the issues don’t just pop up. A wrong change doesn’t show as a failed unit test. It surfaces in the middle of the supply line: as a scrapped lot, or as a tool that unexpectedly idles on a production line.
Search for how to understand an unfamiliar codebase and you’ll get the same method every time: build it, run it, set a breakpoint. Step through these until you get a basic understanding of how things work. Good advice, for an environment where the program runs in front of you.
The precondition is missing though. You can’t put a wafer handler on your desk or attach a debugger to a customer’s tool mid-production. The camera and the corresponding visual data your code expects can’t be on your bench. And the fab host your SECS/GEM layer links to is a system you won’t have clearance for. The method is sound, but because of one unavailable step the process breaks down. For background on how these codebases get this way, we covered the C++ stack inside semiconductor equipment separately.
What you do have, and where each one stops
Here are four partial substitutes, along with their limits.
| What you have | What it covers | Where it stops |
|---|---|---|
| Simulators / virtual hardware | Sequencing logic, state transitions, protocol handshakes | Timing, sensor noise, real-life failures that only appear when using the tool |
| Hardware-in-the-loop rigs | Close to the real thing, when you get time on one | Rarely configured like the customer’s tool |
| Log replay from the field | The actual failure you’re chasing, with real telemetry data | Post-hoc. You see what happened, not what the code was considering |
| A colleague who knows the module | Fastest path, where it exists | Often missing. It’s usually why you’re looking up alternatives |
A good simulator covers more than outsiders assume, but none of these can replace the real thing.
The workflow
Here’s a general approach that you can fine-tune for your own workflow. This should apply to any IDE or software environment you have.
1. Start from the entry point that matters to your task, not the top of the tree. There’s no useful main to read down from in a machine application. Pick the thing you were asked about: the step in the recipe that shows unusual behavior, the instance where the alarm fires, or the SECS message that receives the bad reply. Reading a large codebase from the beginning is usually too time-consuming to produce any relevant understanding.
2. Follow the symbol, not the file.
The folder layout in a twenty-year-old codebase only records team history and merge accidents. It doesn’t describe the design or the architecture. The thing you need may be four folders from what calls it. Navigate by definition and reference instead of browsing the tree.
3. Find every reference before you change anything.
The safety move you definitely should be doing. Before touching a function, a member or a constant, get the complete list of everything that relates to it. (Not just a text search that finds twelve of the fifteen call sites.) In equipment code the thing you’re tempted to delete is usually a timing workaround or a fix for a failure that nobody bothered to document. Use the reference list so you don’t miss these.
4. Use the call hierarchy to find the seam.
Trace upward until you leave your module. Find where your code stops and the machine starts. This tells you the blast radius of a change. If the scope is narrow you can move quickly; if not, you at least know that before you commit.
5. Map the third-party SDK boundaries.
Vision, motion and host communication are usually vendor libraries. Knowing where your code ends and where a vendor library like HALCON or VisionPro begins, or the motion controller’s API, tells you which behavior you can change and which you can only work around.
6. Read the conditional-compilation variants.
Multi-tool, multi-customer and multi-region configurations mean the code on your screen may not be the code that reaches the tool you’re debugging. Confirm which variant is built for that machine before you trust what you’ve read. This avoids losing hours to debugging code that was never running.
The general-purpose version is how to navigate a large or legacy C++ codebase.
Why this codebase fights your tools
Steps 2, 3 and 4 assume your IDE can answer accurately, and that assumption gets tested hard here.
Size is the first reason: building a complete picture is a real computational cost. Pile on the macros and conditionals, layered and often generated, doing structural work rather than swapping constants. Then vendor SDK headers, large and deeply nested and outside your control.
The fourth matters the most. With this many configurations, some frequently don’t fully build in a given developer’s environment: a missing vendor SDK, a config targeting hardware you don’t have, a variant that only compiles on the build server. That’s how machine software usually looks, not an edge case.
It’s also where a lot of tooling quietly degrades, because most code intelligence is built on a successful compilation. Put simply: if a tool needs your project to build before it can tell you where a symbol is used, it goes quiet at exactly the moment you most need it.
To be fair to Visual Studio, Microsoft is working this problem. The whole codebase indexing work in the Insiders builds targets large C++ solutions, with faster navigation and colorization the reported gains, Find All References among them. It’s a preview, rolling out gradually.

The normal state of a machine-software checkout is partly unbuildable. What a tool does at that moment is the whole difference.
How much your current workflow actually costs
A team maintaining a twenty-five-year-old tool codebase has had twenty-five years to stop noticing it. Nobody times how long it takes to find where a symbol is used. It doesn’t feel inefficient because that’s how the job was always done.
Measure it anyway, with your own numbers rather than ours. Over one working day, try to track the following:
- How many times did you go looking for where something is used, and how long did each search take?
- How many did you give up on and work around, with a defensive check or by leaving the code alone?
- On the last unfamiliar module you touched, how long between opening the file and making a change you were confident in?
Multiply by team size and by the weeks in a quarter. We’re deliberately not publishing a figure for this one, because the only number that will persuade anyone at your company is the one from your own codebase.
And the bigger cost isn’t just slow navigation. It also includes changes committed on partial information, a risk that grows with the age of the codebase and with distance from whoever wrote it.
What code intelligence has to do to survive this
So what would a tool need to be useful here rather than merely present?
It has to resolve symbols in code that doesn’t fully build, because that’s the normal state of the checkout. It has to stay responsive at a size where rebuilding its understanding from scratch is expensive. And it has to give the same answer twice, because a result that varies between runs isn’t something you can base a change on.
Determinism is non-negotiable in an industry where everything is organized around repeatability.
Visual Assist is a C++ extension for Visual Studio built around its own parser, designed for code intelligence rather than compilation. It doesn’t need to build your project, only to understand it, so it keeps resolving symbols, references and definitions through code that won’t compile. And because a parser is an algorithm rather than a model, the same code gives the same answer, every time.
For the lead: ramp time and knowledge risk
If you’re deciding whether this needs your attention, the argument is ramp time and concentration of knowledge.
Google researchers studying their own onboarding, in work published in IEEE Software, found the three biggest drags on ramp-up to be learning a new technology, poor or missing documentation, and finding expertise. A decades-old control codebase supplies all three. DX’s benchmark across roughly 400 companies puts the average time to a new engineer’s tenth pull request at 33 days as of April 2026 — well short of being trusted with recipe sequencing on a shipping tool.
In equipment software, “the developer who understood that module left” isn’t a retention statistic. It’s an operational exposure on a product still shipping. Roger Swann’s inherited sensor-testing system is our published version: the original developer unreachable, no chance to ask how any of it worked, and the system needing major changes anyway.
That’s the opening distance measured a different way. The machine is somewhere else, and so, eventually, is the person who knew why the code looks like that. What’s left is the code, and how well you can read it.
The first 90 days on an inherited C++ codebase. The general counterpart of this blog. Shares a four-phase method for surveying, navigating, contributing to and eventually being at home in a codebase you didn’t write. Get the free guide.
FAQ
Why does IntelliSense struggle on equipment control codebases? Four reasons compound: the number of translation units, heavy and often generated macro use, large vendor SDK headers, and configurations that don’t fully build in a given developer’s environment. Code intelligence built on a successful compilation degrades when that last one isn’t met.
How long should ramping onto a legacy C++ control system take? Longer than most managers plan for. DX puts the average time to a new engineer’s tenth pull request at 33 days as of April 2026, across a sample of 400 large organizations. A tenth pull request is a long way short of being trusted with recipe sequencing on a shipping tool, and a decades-old control codebase adds to that distance rather than closing it. Google’s research names missing documentation and finding expertise among the three biggest drags on ramp-up.
Will a C++ productivity extension conflict with Copilot or our vision SDK? Visual Assist and GitHub Copilot Chat have worked side by side since Build 2024.2, when the inline-chat input conflict was fixed. The one thing worth knowing: Visual Assist’s typing-suggestion popups and Copilot’s tab-completion compete for the same key, so most people who run both turn one of the two off and keep it for navigation and refactoring. Vision and motion SDKs are ordinary C++ libraries as far as an IDE extension is concerned, so there’s no interaction beyond the usual header parsing.
How do you justify developer tooling spend to management? On ramp time and change confidence. The numbers that persuade come from your own team: how long a new engineer takes to make a confident change, and how often changes ship without a full picture of what they touch. Both are measurable in a week of tallying.

1 Comment