LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #36: Touching Legacy Code – When a refactor effort becomes a disaster
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

Blog #36: Touching Legacy Code – When a refactor effort becomes a disaster

The story of an 'itchy hand' wanting to clean up old code, ending in an all-nighter to rollback the system.

TP
Truong PhamSoftware Engineer
PublishedJuly 10, 2024

1. The Context

It was a peaceful Tuesday afternoon when I received a small ticket: "Change the color and display an additional data field on the order management page." This project was a massive 5-year-old monolith, running on React Class Components mixed with a mess of Redux Saga and libraries that had long since "retired."

When I opened AdminOrderList.tsx, I saw 1,500 lines of code. Endless event handlers, state variables named data1, data2. I muttered: "Good heavens, who wrote this mess?". My Clean Code sensibilities were triggered. I decided not just to fix the bug, but to refactor it into Functional Components and use Hooks to make it "modern."

I was confident. I thought I understood the data flow. But when I hit deploy to the Staging environment, the entire management page turned white. undefined errors were dancing everywhere. Pressure mounted as my boss asked: "Why did a simple color change crash the admin page?".

2. Foundational Knowledge

In React, converting from Class Components to Functional Components isn't just about changing this.state to useState. The biggest difference lies in Lifecycle and Closure.

In a Class, you have componentDidUpdate, where you can compare prevProps and this.props. In Hooks, you use useEffect. If you don't understand how useEffect captures variable values, you can easily create infinite loops or use stale data. Legacy code is often full of underlying "side effects" that only the old lifecycles could keep in check.

3. The Specific Problem

The system managed thousands of orders daily. A massive file with dozens of overlapping dependencies. The issue appeared when I tried to replace the deprecated componentWillReceiveProps.

// Old code (Class Component)
componentWillReceiveProps(nextProps) {
  if (nextProps.orderId !== this.props.orderId) {
    this.fetchOrderDetail(nextProps.orderId);
  }
}

// My refactored code (Functional Component)
useEffect(() => {
  fetchOrderDetail(orderId);
}, [orderId]); 

Looks correct, right? But the "trap" was that the old fetchOrderDetail function internally called setState for an array of other dependencies, which indirectly triggered an orderId change through some weird Redux Saga middleware. The result was an infinite loop that froze the user's browser.

4. How I Handled It

I started by panicking. I tried adding if checks to block logic, but the more I added, the messier the code became. I had assumed the old logic was wrong just because it was "ugly." That was my biggest mistake. Code might be ugly, but it's running, which means it has survived thousands of real-world test cases I didn't even know about.

I used Chrome Profiler to see why the component was re-rendering 100 times per second. I discovered a change in a filterStatus variable deep in the store that I had never noticed.

Finally, after 4 hours of debugging, I had to accept a painful reality: Step back. I aborted the entire Functional Component refactor. I went back to the old Class Component file, swallowed my pride, and added exactly 2 lines of code to fix the color and add the data field as originally requested.

5. Trade-offs

The "patching" solution on the old code stabilized the system immediately. But it left a major drawback: the Technical Debt was still there. That 1,500-line file remained as hideous as ever, and the next person would still have to endure it.

If I were starting over, I wouldn't choose to tear down and rebuild a large component. I would break it into smaller parts, wrap the old parts in Error Boundaries, and perform an Incremental Refactoring instead of trying to be a "cleanup hero" in a single afternoon.

6. Lesson Learned

I now understand more clearly: Never refactor dirty code unless you have Unit Test coverage for it.

  • Advice: When touching Legacy Code, treat yourself as a surgeon. Cut exactly where you need to cut; don't try to replace the patient's entire brain unless you're sure you can reconnect the nerves.
  • When to apply: Refactor when you have enough time to write tests and understand 100% of the side effects.
  • When not to: When the deadline is near, when there are no tests, or when your only reason is "this code is just so ugly."

Sometimes, writing a "slightly ugly" but safe piece of code is the mark of a Senior who knows how to quantify risk, rather than a Junior who always wants everything to be textbook-perfect.


Notes on humility before the code that came before.

Series • Part 36 of 50

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #37: The Reusability Trap – When a 'multi-purpose' Component becomes a burden
Blog #35: Clean Architecture – The Dream and the Harsh Reality
31Blog #31: 1 AM and the Search for the 'Missing' File32Blog #32: Friday Afternoon, 800 Lines of Code, and a Miscalculation33Blog #33: 10 PM and the 'Infinite Loop' of Punishment34Blog #34: My Machine Runs Smooth, the User's Machine Smokes35Blog #35: Clean Architecture – The Dream and the Harsh Reality36Blog #36: Touching Legacy Code – When a refactor effort becomes a disasterReading37Blog #37: The Reusability Trap – When a 'multi-purpose' Component becomes a burden38Blog #38: Micro-frontend Reality Check – When theoretical glory meets harsh reality39Blog #39: The 'Will Fix Later' Promise and the Compound Interest of Technical Debt40Blog #40: Don't try to be smart – Write code for humans, not for machines41Blog #41: The 2 AM Panic and Infinite Question Marks
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles