Refactoring in the Age of Unlimited Code Generation
Architectural Decisions at the Speed of Anxiety
My barber has this theory about hair. He says it doesn't actually grow, it just accumulates. Like dust. Or regrets. Or npm dependencies.
I was thinking about this while watching Claude add the 47th utility function to my supposedly "simple" app. Each function made perfect sense at the time. Each one solved a real problem. But somehow, looking at them all together, I had created... well, I'm not sure what I created, but a mess was definitely starting to accumulate.
My barber's always sweeping. Even when there's no hair on the floor. "Preventative maintenance," he says. I nod like I understand, but really I'm thinking about Javascript.
The Waterfall, The Skateboard, and The Frankenstein's Monster
You know that meme about building cars?
Waterfall methodology gives you wheels, then an engine, then eventually a car. Very sensible. Very linear. Very much like how you would never actually build a car.
Agile gives you a skateboard, then a scooter, then eventually a car. Because apparently transportation is just transportation, right?
AI? AI gives you a fully functional car with nineteen cup holders, a built-in aquarium, and (for reasons that will never be explained) the ability to communicate with satellites. Also, the car is held together with zip ties. But it works.
The difference is speed. What used to take months now takes hours. Claude can write more code in an afternoon than I used to write in a week. Pretty good code, too. Code that works. Code that handles edge cases I hadn't even thought of. Code that handles edge cases that don't exist yet. Code that's prepared for edge cases in alternate dimensions.
But there's a problem. AI doesn't know when to stop adding features. You say "make this button blue" and somehow you end up with a color picker, a theme engine, and a dissertation on color theory embedded in the comments. Each line makes perfect sense in context. It's just that the context has somehow expanded to include the entire visible spectrum and several invisible ones.
The Old Rules Don't Apply
Let me be clear about what kind of refactoring I'm talking about. Not the "extract method" kind where you pull out ten lines into a nicely named function. Not the "rename variable" kind that makes code more readable. Those are the domain of the AI agents now. (They're very good at it. Too good...)
I'm talking about architectural extraction. Taking entire subsystems and giving them their own existence. What we might have called "modularization" or "decomposition" in the past, but now needs to happen at AI speed.
We used to wait months or years before a module proved it deserved independence. You know the evolution: that utility file grows and grows, eventually becomes a folder, then maybe (if you're lucky) someone suggests making it a separate package. Like watching your kid grow up and move out, except your kid is a logging system and it's been living in your basement for three years.
With AI, you hit those inflection points in days, not months. Sometimes hours. (I've seen it happen in minutes, but I try not to think about that.)
The New Senior Developer Skill
Last week, I watched Claude build a data sync system for APM. In four hours, it went from "simple WebSocket manager" to "complex distributed systems masterpiece." Automatic reconnection, message queuing, health checks, connection pooling, consensus protocols(???)...
I'm sitting there, coffee cold, thinking: "This isn't part of my app anymore. This is... something else. Is this just three different libraries wearing a trench coat, pretending to be a feature?"
Here's what I think the role of senior developers will become: pattern recognition at the module level. Not "is this code good?" but "is this code in the right place?"
When Claude builds you an authentication system inside your todo app, you need to recognize: that's not part of your todo app. That's a separate thing that your todo app uses. It's like finding a full restaurant kitchen in your studio apartment. Sure, it works, but maybe it belongs somewhere else.
I work alone now, mostly. Talk to myself. Ask questions like "Is this authentication system trying to solve world hunger?" The walls don't answer, but sometimes Claude does…
The Extraction Economy
Anyway, I've started pulling modules out of projects earlier than I used to. Much earlier. Embarrassingly earlier.
That WebSocket manager? Getting ready to extract it after about a week of use. Soon it will be a separate package with its own tests, its own documentation, its own reason for existing. My app will just use it. Simply. Cleanly. Without carrying around all that complexity like a backpack full of rocks I might need someday.
This isn't premature optimization. It's fighting entropy at the source. (Or maybe it's premature optimization and I'm just better at lying to myself now. Both can be true.)
When you extract early:
Your main app stays focused and understandable
The extracted module can evolve without breaking your app
You have fewer places for things to go wrong
Claude has less context to juggle in future sessions
Each piece becomes more understandable in isolation
The last point is crucial. When everything's tangled together, neither you nor AI can reason about it effectively. But a module with clear boundaries and a single purpose? That's comprehensible to both human and machine intelligence. (Well, as comprehensible as anything is these days.)
The Boundaries That Matter
The magic happens at the boundaries. A well-defined interface between your app and a module is worth a thousand code reviews. Maybe two thousand. I haven't done the math.
When everything's in one codebase, Claude tries to be helpful by connecting things that shouldn't be connected. Your authentication starts knowing about your UI. Your data layer starts making assumptions about your business logic. Your logger develops opinions about your color scheme.
But when you have clear module boundaries? Claude can't cross them without your explicit permission. Each piece evolves independently. Each piece stays focused. Each piece remains blissfully unaware of the satellites. (Why is it always satellites?)
Why This Feels Wrong (But Isn’t)
This kind of refactoring feels weird because we're used to code earning its extraction through time and pain. Like a trial by fire, except the fire is bugs and the trial is explaining to your manager why everything's broken.
But AI compresses months of development into days. Which means you need to compress months of architectural decisions into hours. You can't wait for the code to "tell you" when it needs extracting. By the time it's obviously a problem, you're already drowning in complexity. And the complexity has developed its own ecosystem. With documentation.
My process now looks something like this:
Let Claude build the feature with all its bells and whistles
Use it for a day (sometimes just hours)
Stare at it asking "What here isn't actually about my app?"
Extract those pieces immediately
Keep the simple interface, move the complex implementation
It feels weird to make a library out of code that's only a day old. But with AI, a day's worth of code can be a month's worth of complexity. Or a year's worth. Time doesn't mean what it used to mean.
The Open Source Code Part
Speaking of extracting useful things from overly complex codebases...
We just released this Tauri plugin for communicating with MCP servers. It's exactly the kind of over-engineered marvel that Claude loves to create, except we trapped it in its own package where it can't hurt anybody.
It handles all the complexity that desktop apps need when working with MCP: process management, connection and disconnection, tool calling, error handling. All the stuff that Claude will eagerly build for you whether you ask for it or not. (Especially if you don't ask for it.)
It's on GitHub now. Use it, fork it, learn from it, or just appreciate that it's not cluttering up Protocollie anymore.
More importantly, it's an example of what I'm talking about. Code that was born inside one project but clearly belonged somewhere else. Recognized early, extracted early, and now useful to everyone.
But that's not really the point. The point is that code doesn't just grow anymore. It accumulates. At superhuman speed. And if you're not constantly sweeping, constantly extracting, constantly refactoring, you'll wake up one day and find your simple app has become a distributed system with opinions about color theory and the ability to talk to satellites.
I've found for many fixed, ChatGPT usually suggests adding more code instead of simply debugging.
Two weeks ago I broke my app into 3 pieces core business logic, CLI and a rails engine. Last night I started putting it back together bring the core into the cli. It felt dirty but I did it anyway. This morning after reading your post I convinced that the dirty feeling I felt was right. Core is coming back out. Just got to juggle the github repos.