Patrick Leckey
Cover image

In Praise of the Ugly: Why Legacy Code Is Part of the Job

Legacy code is the software equivalent of inheriting your great-uncle’s cottage: it’s crooked, a little moldy, and you’re afraid turning on the tap will summon something with tentacles. But it’s by a lake, it has a dock, and - minor detail - your family adores going there every summer. You don’t get to burn it down just because the plumbing makes no sense.

Anyone who’s been in the industry longer than the lifespan of a fruit fly has encountered legacy code. It’s everywhere. It’s the quiet majority of all software on Earth. It powers airlines, banks, governments, hospitals, insurance companies, payroll systems, and grocery store scanners. It processes your credit card, prints your boarding pass, and allows you to buy socks online while ignoring your loved ones. It is the plumbing of modern life.

And sometimes… it’s awful.

Let’s not pretend otherwise. There’s legacy code that looks like it escaped from a haunted house: global variables breeding like rabbits, functions the size of novellas, business logic that clearly came from negotiations among three angry wizards. It’s fine - no one is saying you must love legacy code. You are, however, expected to deal with it like a professional adult who gets paid real money in exchange for working on real systems that serve real people.

That’s the deal.

This article is about why that deal is worth making, and why “someone else wrote this” is not an acceptable stance for anyone claiming the title professional software developer.

Let’s get into it.

Legacy Code: The Price of Success

An usually polite truth: you only get legacy code when your product has survived long enough for people to rely on it.

A brand-new startup with a clean codebase is a newborn. Cute, pristine, and one bad quarter away from bankruptcy. Legacy code is the opposite: it has customers. It has years of revenue behind it. It has survived market swings, bad leadership decisions, layoffs, mergers, and the occasional sabotage by interns making heroic last-day commits.

Look at any successful software company beyond the chasm of Series B and you’ll find legacy code. It’s the cost of winning. If a product has been around long enough that people depend on it, there is legacy code in there. Somewhere. Probably everywhere.

If you’re lucky, it’s merely quirky. If you’re unlucky, it’s a labyrinth with a Minotaur named “production outage.” Either way, it exists because something about that product worked long enough to matter. Messy or not, somebody paid for it, customers continue using it, and it makes money.

That alone makes it worth respecting.

“It’s Not My Code” Is Worse Than Useless

Every developer has, at some point, opened a file, stared into its horrors, and said “what on Earth were they thinking?” Or the more popular version: “who the hell wrote this?”

The immediate emotional response is fine. Eye-rolling is a natural part of debugging. A little loud sighing is healthy. A short coffee break to process the trauma is acceptable.

But the refrain “someone else wrote this” is a dead end. It helps no one. It doesn’t fix bugs, doesn’t increase velocity, and does nothing to improve the experience of the customer who is paying the bills.

You accepted a job building, maintaining, and operating software. The code in the repo is your code now, for better or worse. You’re married to it. You don’t get to claim “it was like this when I got here” after the ship sinks. If the kitchen’s on fire, you don’t search the warranty documents to see who’s at fault - you grab a fire extinguisher.

If you work in product development, that is your professional responsibility.

You inherit the code, the data models, the deployment scripts held together with chewing gum, and the Jira tickets that smell like sadness. You may not love them, but they’re yours. Welcome to the team.

Why Legacy Code Hurts

Legacy code is painful for three main reasons:

It’s confusing.

It’s fragile.

It’s slow to change.

The first problem - confusion - is simply the consequence of entropy. Software ages, people leave, requirements drift, tribal knowledge fades, and suddenly no one knows why a function called processFee() also provisions a user account and sends a fax to a fish cannery.

Fragility is caused by the same forces. If you don’t understand how something works, you don’t understand the side effects. Change one thing and suddenly tax rates in Peru break. In a well-designed system, components are modular, isolated, testable. In legacy systems, they are sometimes duct-taped together like a cult movie prop.

Slow change is the result. The ability to deliver new value is throttled by the time spent navigating existing complexity.

So yes, legacy code can make your day job harder. But that’s part of the gig.

If you want no legacy code, no dirty constraints, and no “unknown unknowns,” you can go build weekend toy apps that no one uses. But please don’t then call the professional world irrational for not running like your side project that three friends starred on GitHub.

Real software lives long enough to get ugly.

The Myth of the Rewrite

Every developer has daydreamed about rewriting the entire system. Clean slate. New architecture. Fresh stack. New tools. Dark mode editor. Suddenly everything is right with the world.

The rewrite fantasy is like imagining that if you sell your collapsing house, you can buy a mansion with the same money. Rewrite advocates paint a picture of a brave new beginning where all the sins of the past are washed away, productivity doubles, morale skyrockets, and the hot water works.

This rarely happens.

Rewrites are seductive because starting fresh avoids facing the hard reality: complexity is not in the code. It’s in the business rules, workflows, customer expectations, and interactions that system has accumulated over time. You can delete the code, but you can’t delete the business logic. And that’s where the real monsters live.

Most rewrites fail or stall out halfway, leaving two bad codebases instead of one. Meanwhile, the business continues operating, demanding new features, regulatory compliance, and bug fixes. The rewrite falls further behind reality every day.

It’s usually better to improve than replace.

Complaining Is Allowed. Complaining While Fixing Is Better.

Let’s be honest: complaining is cathartic. Every workplace has people who vent about the legacy system. I’m one of them. You probably are too.

The important part is whether the complaining is accompanied by action.

Saying “this code is awful” is fine. But if you stop there, you’re just adding noise. The job is to improve the situation. Identify bad patterns. Add tests. Remove dead code. Strangle parts of the system with new services. Document what you learn. Create upgrade paths. Migrate gradually. Celebrate small victories.

A professional complains while fixing. A hobbyist complains while wishing.

Legacy code improves only when someone decides to take responsibility for making it better. Not perfect - better.

You Don’t Need Permission to Improve Your Tools

There’s a myth that you need buy-in from leadership to start improving the system. Good news: you usually don’t. Unless your CEO reviews your pull requests personally, you have more autonomy than you think.

You can:

  • Add tests
  • Clean confusing logic
  • Update documentation
  • Improve local dev tooling
  • Split a behemoth function into smaller pieces
  • Fix naming
  • Introduce linting
  • Add metrics
  • Write migration scripts

None of these require executive approval. They require discipline.

Long-running improvement is done quietly, intentionally, and incrementally. You don’t need a five-year plan. You need a habit.

The Hidden Joy of Legacy Code

Despite the pain, there’s a weird pleasure in taming a beast. Few things are as satisfying as cleaning up a code path that terrified everyone. Or discovering a bug that’s haunted production for years and finally exorcising it. Or peeling apart a thousand-line function into something understandable.

Craftsmanship isn’t only about new builds. It’s also about restoration.

Some developers think legacy work is beneath them. That they’re above “maintenance” and “refactoring.” These folks often prefer greenfield - only new things, on new stacks, using new frameworks. And that’s fine, if you want to live in labs and prototypes.

But the real world has roots.

The best engineers I’ve known were excellent because they could reason about messy systems. They understood history. They could trace decisions from years prior. They could diagnose issues in places no one had touched in a decade.

Legacy code forces you to develop an engineering intuition you won’t get from writing fresh CRUD apps.

It makes you better.

How to Work With Legacy Code (Without Losing Your Mind)

Some practical advice:

1. Read before changing

Don’t blindly hack. Understand what the code is doing. Trace pathways. Look for side effects. Resist the urge to “just fix it” in ten minutes, unless you like causing outages.

2. Add safety rails

Tests are your friends. Logs are your alibis. Metrics are your lifelines.

3. Make small changes

Forget giant PRs. Tackle one workflow at a time. Small changes are easier to review, safer to deploy, and simpler to roll back.

4. Remove unknown risk

Identify hotspots. Areas that break often. Code paths few people understand. Instrument them. Add tests. Slowly introduce clarity.

5. Document the weirdness

Legacy code often hides business rules that don’t exist anywhere else. Write down what you learn, even if it’s only in comments.

6. Improve the shape of the code

Good architecture isn’t built in one go. It’s nudged into shape over time. Extract services. Decouple modules. Introduce boundaries.

7. Know when to stop

Not every part of the system deserves love. Focus on areas that matter most to customers and business outcomes.

Legacy Code Teaches You the Business

The ugliest parts of the system often encode the most valuable knowledge. That hideous class that reads like a Choose-Your-Own-Adventure novel? It’s probably implementing a gnarly business rule that no one else remembers. Fixing and restructuring it will teach you more about the business than a month of product meetings.

Engineers who understand the business are worth their weight in Series C funding.

When you excavate legacy code, you’re not just refactoring. You’re learning why the business exists and how customers actually behave.

Gorgeous.

Legacy Code Keeps You Employed

If everything were easy, replaceable, and well-abstracted, your job would be outsourced by lunch.

Legacy systems take real skill to maintain and evolve. That skill commands value. You can build a career out of being the person who rehabilitates software others fear.

Anyone can build something new. Fewer can shepherd an old system through three generations of customers without breaking the world.

If you can do that, you’re not just employable - you’re indispensable.

Don’t Wait For Appreciation

Working on legacy code is like cleaning public bathrooms. Everyone benefits, no one thanks you, and people assume it’s somehow your fault they’re not nicer.

If you need applause, join theatre.

This work is invisible because success looks like… nothing happened. Customers keep using the system. No outages. No drama. And then you deliver new features faster. The code is easier to understand. New developers onboard more quickly. The business gains speed.

Good work on legacy code pays dividends quietly and consistently.

If you crave glory, you can brag about rewriting everything from scratch later. But deep down, you’ll know the truth: the real hero is the person who incrementally tamed a beast without stopping business.

Legacy Code Is Real Engineering

Anyone can design a perfect system on a whiteboard. You want to impress me? Refactor a critical production module that four companies depend on, without downtime, while shipping three new features and keeping angry customers from setting Slack on fire.

That’s engineering.

It’s the messy interplay of technical and business concerns. It’s the practice of improving systems while they’re running, while requirements are changing, with limited time and imperfect knowledge.

It’s the craft.

Professional engineers don’t whine their way out of hard problems. They anchor themselves, figure out what’s important, and deliver.

Legacy code is not a distraction from engineering. It is the job.

Respect the Beast

Legacy systems are uncomfortable precisely because they’re real. They reflect years of changing requirements, market forces, developer turnover, and business survival. They are the battle scars of success.

You don’t have to love the code. I certainly don’t. But you do have to respect the context. The system worked well enough, long enough, for you to show up and complain about it.

Congrats. You have a job because of this mess.

So complain. Loudly. It’s healthy. Then roll up your sleeves, make a plan, and get to work. Bitching is fine. Bitching while fixing is better.

Someone has to tame the beast. Might as well be you.

RSS