Introduction — Why This Was Worth Writing About
Recently, I worked on modernizing a legacy Google Universal Analytics event-tracking system in production. On the surface, it looked like a straightforward refactor: remove jQuery, introduce TypeScript, and clean up the architecture.
In reality, it was one of the higher-risk changes you can make in a mature system.
Analytics code doesn’t fail loudly. It fails silently. And when it fails, you don’t get errors—you get bad data, broken dashboards, and leadership making decisions off the wrong signals.
This post isn’t a tutorial. It’s a reflection on how I approached a risky refactor under real production constraints—and what that process reinforced for me about senior-level engineering judgment.
If you’ve ever touched legacy code that “just works” but nobody wants to own, this will probably feel familiar.
Reframing the Problem: Analytics Code Isn’t Just JavaScript
The biggest mindset shift wasn’t technical—it was conceptual.
Legacy analytics code often gets treated as “just frontend JavaScript.” In practice, it’s closer to infrastructure. It sits between product behavior, ad systems, and business reporting. A small change in payload shape or timing can ripple all the way to revenue tracking.
The original system relied heavily on jQuery and unstructured scripts that had grown organically over the years. It wasn’t elegant, but it was battle-tested.
The mistake would have been assuming modernization meant improvement by default.
Instead, the real goal became clear early on:
Modernize the architecture without changing behavior.
Not “make it better.”
Not “clean it up.”
Make it safer to maintain—without breaking trust in the data.
What Actually Worked (and Why)
Rather than attacking everything at once, I focused on a few guiding decisions that shaped the entire refactor.
1. Break the monolith by responsibility, not technology
Instead of one large analytics file, the system was decomposed into dedicated handlers:
- Event delegation
- In-view tracking
- Content-consumed tracking
- Ad slot tracking
Each handler owned one concern and nothing else.
2. Centralize payload logic to protect parity
All analytics payload construction was moved into a single parity layer. This became the contract between old behavior and new architecture. Every handler fed into it, and nothing bypassed it.
3. Prefer defensive design over ideal abstractions
Fallback paths were treated as first-class logic—not edge cases. If a browser feature didn’t exist, behavior still had to match legacy output exactly.
4. Optimize for confidence, not elegance
There were moments where a simpler abstraction was possible—but only at the cost of observability or testability. Those shortcuts weren’t worth it.
None of these decisions was glamorous. All of them reduced risk.
Tools & Techniques (Secondary to Judgment)
TypeScript was a key enabler, but not the star of the show.
The real value came from how it supported:
- Clear contracts between modules
- Explicit payload shapes
- Safer refactors with confidence from tooling
Testing used a DOM-simulated environment, so browser behavior could be validated without relying on production guesswork. Fallback paths—especially around visibility tracking—were tested intentionally, not implicitly.
The takeaway here is simple: tools amplify good decisions. They don’t replace them.
Real-World Constraints That Shaped Every Decision
This work happened under constraints that tutorials rarely acknowledge:
- No breaking changes allowed
- Exact analytics payload parity required
- Strict CI/CD enforcement
- Security and compliance scans
There was no room for “close enough.”
Even non-functional issues—like Node version mismatches or test environment quirks—had to be resolved cleanly before anything could move forward.
This is where experience shows up. Not in knowing more tools—but in understanding which problems matter under pressure.
Using AI Without Abdicating Engineering Judgment
One part of this work that’s worth calling out is how AI fits into the process—because it wasn’t used as a shortcut.
The refactor was driven by a written specification that defined non-negotiables: exact payload parity, fallback behavior, and production constraints. That spec became the contract—not just for the code, but for how AI was used.
AI helped accelerate reasoning:
- validating assumptions while breaking the system into handlers
- pressure-testing edge cases before writing tests
- catching inconsistencies between legacy behavior and new abstractions
But it never replaced decision-making.
Every meaningful choice—architecture boundaries, fallback strategies, parity enforcement—was owned and verified manually. Tests, not AI output, were the final authority.
This spec-driven approach mattered because analytics code doesn’t forgive “almost right.” The combination of a written spec, exhaustive tests, and AI as a reasoning assistant allowed the refactor to move faster without increasing risk.
Used this way, AI wasn’t a code generator—it was a force multiplier for judgment.
Testing as the Safety Net (Not the Finish Line)
The final implementation was supported by over 200 unit tests across multiple suites.
That number wasn’t a goal—it was a consequence.
Each test answered a specific question:
- Does this still fire when the DOM changes?
- Does fallback logic behave identically?
- Does this payload match legacy output byte-for-byte?
The tests didn’t exist to prove correctness in theory. They existed to make future changes safer.
That’s the difference between testing for coverage and testing for trust.
Results & Impact
What improved:
- A modular, maintainable architecture
- Strong typing and explicit contracts
- Clear ownership boundaries
- High confidence in future changes
What stayed intentionally the same:
- Event timing
- Payload shape
- Business logic
- Reporting outcomes
That balance was the real win.
Modernization didn’t mean reinvention—it meant durability.
The Lesson I’d Share With Other Engineers
If I were advising someone earlier in their career, I’d emphasize this:
Senior engineering isn’t about rewriting systems. It’s about knowing when not to.
The most impactful work often looks boring from the outside. It’s quiet, careful, and constraint-driven.
But it’s exactly the work that keeps systems—and teams—moving forward without surprises.
Closing Reflection
This refactor reinforced something I’ve learned repeatedly:
Clean code matters—but trust matters more.
When you’re working on systems that underpin business decisions, safety and parity outweigh novelty every time.
That mindset scales far beyond analytics. It applies to any legacy system that still carries real weight.
And learning to navigate that responsibility is one of the clearest markers of growth as an engineer.





Leave a Reply