Refactoring Without Changing a Single Pixel

Adding new features is exciting, but sooner or later every project reaches a point where the code itself needs attention.

After getting the hair rendering to a place I was finally happy with, I decided it was time to stop adding features and start improving the codebase instead.

This time, I had one simple rule:

Not a single pixel of the final result should change.

The goal wasn’t to build something new—it was to make the existing code cleaner, easier to understand, and easier to maintain.

Cleaning Up the Past: Removing Dead Code

ME

The rendering feels pretty stable now.

Let’s start by cleaning up unused functions, variables, and old code.

But we can’t change the behavior.

Not even a little.


Claude

Sounds good.

I’ll scan the entire project and build the call graph first.

Anything that isn’t part of the execution path—unused functions, unused variables, and unreachable code—can be removed safely.


A few minutes later…


Claude

I found more legacy code than I expected.

Some compatibility functions are no longer referenced, and a few blocks are permanently disabled with conditions like if(false).


ME

So we can delete all of that?


Claude

Yes.

None of it is reachable anymore.

Removing it won’t affect the application’s behavior.


One by one, the dead code disappeared.

Unused functions.

Unused variables.

Legacy compatibility code.

Even blocks that had literally become impossible to execute.

Nothing changed on the screen.

But the code became noticeably easier to read.

Sometimes the best feature is simply removing code that no longer serves a purpose.


Eliminating Duplication: One Logic, One Place

Once the dead code was gone, another issue became obvious.

The same logic kept appearing in multiple places.


ME

The RGB averaging code looks familiar…

And aren’t we calling the renderer the same way over and over?


Claude

Exactly.

Duplicated logic eventually turns into duplicated bugs.

Let’s extract those repeated pieces into shared helper functions.


We started consolidating everything that was doing the same job.

  • RGB color averaging
  • Canvas rendering calls
  • Angle switch updates
  • Debug UI synchronization

No new algorithms.

No behavioral changes.

Just one implementation for each responsibility instead of several copies scattered throughout the project.


ME

The file is noticeably smaller now.


Claude

The biggest benefit isn’t the number of lines.

It’s maintainability.

The next time we need to change one of these behaviors, we’ll only have one place to update.


Breaking Down Giant Functions

With duplication out of the way, the next target was obvious.

Some functions had simply grown too large.

The biggest example was navTo().

Its name suggested a simple navigation function.

In reality, it had gradually become responsible for almost everything.

  • Switching screens
  • Starting the AI analysis pipeline
  • Initializing the style view
  • Preparing the adjustment screen
  • Triggering rendering

It wasn’t really a navigation function anymore.

It was several different systems bundled into one.


ME

This function is trying to do way too much.


Claude

I agree.

It’s violating the Single Responsibility Principle.

We should split it into smaller functions with clear responsibilities.


There was one important requirement, though.


ME

The external API can’t change.

Everything else in the project still has to call navTo() exactly the same way.


Claude

That’s perfectly fine.

We’ll keep the public API intact.

Internally, navTo() will simply delegate the work to smaller functions.

For example:

  • Start the AI analysis pipeline
  • Initialize the adjustment screen
  • Prepare the renderer

The outside world won’t notice any difference.

Only the internal structure becomes cleaner.


When the refactoring was finished, nothing looked different.

The buttons behaved exactly the same.

The rendering produced the same results.

From the user’s perspective, absolutely nothing had changed.

But from a developer’s perspective, everything felt different.

Instead of one massive function trying to manage every task, the project now consisted of smaller functions, each responsible for a single job and working together.

This wasn’t the day I added a new feature.

It was the day I made future features easier to build.

Sometimes the most valuable code you write isn’t new code at all.

It’s the code you simplify so the next problem becomes easier to solve.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *