December 30th, 2024

If I Could Wave a Magic Wand

Rewriting code may not improve quality and can overlook edge cases. An iterative approach is preferred, balancing idealism with current limitations, and focusing on incremental improvements for better outcomes.

Read original articleLink Icon
If I Could Wave a Magic Wand

The blog post discusses the complexities and considerations involved in rewriting code, emphasizing that such efforts may not always yield better results. It references Joel Spolsky's cautionary tale about Netscape's complete codebase rewrite, which may have hindered their recovery. Key points include the uncertainty of improving code quality, the potential for forgotten edge cases, and the time commitment required for a rewrite. The author suggests that instead of pursuing a complete rewrite, an iterative approach may be more beneficial. The post encourages maintaining idealism while also recognizing the limitations of existing code. It draws on Adam Savage's analogy of remodeling a room to illustrate the importance of envisioning an ideal solution, which can help clarify necessary versus accidental complexities in the code. This mental exercise can guide developers in making incremental improvements rather than drastic changes, allowing for a clearer path toward an improved codebase.

- Rewriting code does not guarantee better outcomes and may overlook existing edge cases.

- An iterative approach is often more effective than a complete rewrite.

- Maintaining idealism while recognizing the limitations of current code is crucial.

- Visualizing an ideal solution can help identify necessary complexities in the code.

- Incremental improvements can lead to a better codebase without the risks of a full rewrite.

Link Icon 18 comments
By @smcameron - 4 months
Working on linux storage drivers for a decade or so taught me how to write code iteratively, slowly and methodically morphing it towards my goal, with each step along the way functioning, and an improvement over the previous step. You basically were forced to do things gradually. Upstream generally wouldn't accept a patch that just changed things wholesale, or even a patch that didn't make one "logical" change. Tools emerged to make such a workflow easier, easy, even. First Andrew Morton's patch scripts[1], then quilt[2], and then stgit[3], which I still use to this day on my own projects.

I will say it does help when slowly morphing code that it was all C code. C is very malleable, like clay. I've found code in some other languages I won't name to be less malleable, more crystalline, more necessary to smash to atoms to effect the desired change.

[1] https://lore.kernel.org/lkml/3DB30283.5CEEE032@digeo.com/

[2] https://savannah.nongnu.org/projects/quilt/

[3] https://stacked-git.github.io/

By @MarkusWandel - 4 months
Having had to do the occasional "start with an empty file" kind of rewrite/refactor, both in hobby projects and at work, I just take the "copy/paste" approach.

Start with the structure of what you really want it to be and then flesh it out. Soon discover that a lot of the existing code is OK, anywhere from a simple "for" loop to an entire module, because they work just as well in the new structure and don't have significant technical debt on their own. These can be pasted into the rewrite and possibly adjusted lightly.

There's rarely need for a truly clean sheet.

By @upghost - 4 months
Much as I hate to admit it I can relate to this. When you come onto a new project, the first thing you notice are all of the things that could be improved. But most seasoned developers know that knocking down walls right away violates the whole Chesterton's Fence[1] thing and so you make restrained changes and small commits while learning the system. A few years on when you finally have the mental model and understanding to make the changes... you don't see the inefficiencies anymore. I don't know how to square this circle.

[1]: https://theknowledge.io/chestertons-fence-explained/

By @tikkun - 4 months
This reminds me of a life philosophy.

If you dislike a situation you're in and you try and fix it by switching to a new situation, you'll generally bring with you some of the problems that created that prior situation.

If instead, you bit by bit improve the situation until you feel at peace with it, you'll then either no longer want to move to a new situation, or if you do want to move, you'll no longer bring with you the problems of the prior situation.

Applies to job changes, relationships, projects, goals. And, from OP, applies to architecting software projects.

By @kardianos - 4 months
> Don’t Lose Your Idealism, Ground It.

This is almost universally true. We need morals/ideals, but they must be grounded in reality, in what is. If we just trudge along, we loose any vision for the future that could be better. If we just have idealism when we try to make things better, things usually get worse.

I'm not a constructivist (and definitely not a critical constructivist); I believe reality is knowable and consequential.

So have ideals; ground them in reality.

By @Kilenaitor - 4 months
I enjoyed this post since this is exactly my approach to improvements: imagine the magic want solution, find the deltas between it and current state, code the deltas.

At work I often get tapped to work with folks who struggle with "Better Engineering" ideas (codebase improvements with an eye towards increased productivity). Usually it's just people being unable to come up with any improvements.

I always prompt them: 1. "Is this the best codebase you've ever worked in?" and 2. "If you were to rewrite this from scratch, would it look exactly like this?".

It's amusing how often those two questions trigger a light bulb moment. I of course follow up to ensure their ideas are actually good and grounded (no "let's convert the monolith to microservices") but it does wonders for inspiration.

By @tinthedev - 4 months
I have a somewhat more wordy version of this blogpost as a conference talk I've done pre-COVID (and pre-kids). In my perception, this mostly boils down to reviewing and revising interfaces.

Code that's not well compartmentalized and is full of complex dependency chains and flawed abstractions is hard to work in, and more importantly to the topic at hand: extremely hard to refactor well.

Once the abstractions are shuffled to their own "corners" of the codebase, and you've got well defined modules/services/microservices/foobars... you'll find refactoring to be far less of an investment. It also becomes far less attractive, as a well abstracted module is easy to ignore and forget about.

Of course, it's always best to make these things right the first time. Whenever I kick off a greenfield project, my first code-style objective is to make things easy to delete/remove.

Addendum: I find the worst spaghetti code comes from very dynamically typed languages. All the "easy" coding makes skipping interfaces/abstractions effortless, thus nothing's "doing just one thing well" and it snowballs from there. On the flip-side, when done right, it's quite a joy to write delete-able code in Python, and it makes prototyping and defining boundaries a breeze.

By @parpfish - 4 months
sometimes when a dev pushes for a refactor they'll say that the reason is about building maintainability/best-practices/code-philosophy/whatever but the TRUE reason that they may not even be willing to admit to themselves is that they see something FUN in the refactor.

finding patterns that could be abstracted is fun like solving a tricky puzzle. or getting a chance to play with a new framework or language feature and just seeing what it's like.

i don't know how a manager is supposed to handle this situation, but as a dev once i realize what my true motivation is it becomes a lot easier. i can separate the 'coding for work' from the 'coding for fun' and just put my head down and do the boring work and look for the fun somewhere else.

By @brodouevencode - 4 months
A simplistic take on a complicated subject. Some things, many things, warrant a rewrite. There are several reasons why: 1) the system is not used in the way in which it was designed 2) you've learned a great deal more about the problem 3) duct-tape instead of welds in the initial approach 4) technology obsolescence 5) overly-clever code 6) undocumented code, etc.

Now, I think the 'no rewrite' argument has validity at certain levels. Systems will morph (because life/problems change) but the underlying functions may not need to. Composability and interfaces are wonderful tools to address that problem.

By @physicles - 4 months
This is related to how I define technical debt: the delta between what exists, and what you would write if you could start from scratch and had infinite time. Paying down technical debt is therefore the process of moving the system toward that clean-slate state.

Of course, if you look at all those little deltas between the current and clean-slate states, some of them are much more expensive to live with or solve than others. This definition doesn’t help you decide which changes to make first, only what your North Star should be.

By @naruhodo - 4 months
> 2. There are probably edge cases this code solves that we don’t remember.

But fortunately, I write in-code comments documenting these, because I pay close attention to such details.

> 4. Your own code always feels better to read, because you wrote it. That doesn’t mean it’s actually better to read than someone else’s.

Having that mental context makes your own code easier to read. That's why those comments are so important: they share that context with the next reader. Well-commented code legitimately is better.

By @PaulHoule - 4 months
Kinda funny I was writing a chess engine in Python that was able to beat my tester (who beats the average person but is near the bottom of the bracket at the chess club) with just 6 plies of alpha-beta.

He tells me he'd be happy if it played at the same level but was faster (play more games) and also that if I want to take it to the chess club it has to respect time control. Supporting something like XBoard and UCI would also be a hassle in Python because it needs a comms thread that can interrupt a think thread.

I rewrote it in Java and the process was super-fast because I could cut-and-paste the data in the test suite also I had mastered the signs in the negamax algorithm (I screwed that up and it discovered this https://en.wikipedia.org/wiki/Fool%27s_mate !)

It's different from a lot of applications work because it's really a simple program and doesn't have the panopoly of features that you miss when you try something like

https://mobilesyrup.com/2024/09/24/sonos-employees-app-botch...

the hard part is that right now it is spending roughly equal time in evaluation and managing transposition tables. I think I can speed up eval about 20x which is going to make me code up some kind of specialized off-heap hashtable.

By @blueboo - 4 months
Fred Brooks said it best 50 years ago

> The programmer builds from pure thought-stuff: concepts and very flexible representations thereof. Because the medium is tractable, we expect few difficulties in implementation; hence our pervasive optimism. Because our ideas are faulty, we have bugs; hence our optimism is unjustified.

Humans just have bad intuitions for this problem space, so you have to be consciously empirical; externalize decision factors, track outcomes, articulate hypotheses and be honest.

By @whilenot-dev - 4 months
An old friend once said "Evolution beats Revolution" and that just stuck as mantra in my mind.

> I find that usually (good) programmers enter a new project with idealistic dreams of ripping out the walls; this could be done differently, that could be removed entirely, and so on. Then, later, the longer they stay the more those walls seem familiar, and the idea of changing everything becomes instead a distant memory.

Oh... analogies! In reality those walls will not disappear. Instead, the rooms that have been defined will become inhabited, and you'll notice that you just lack proper resources to remove walls. You'll start questioning whether you're in the construction business, interior designer (walls need decoration!) or just any other tenant for your landlord. You'll never be the needed tornado, so it's just best to hope to discover that the walls are actually made out of cardboard and some tropical storm is coming up after the past dry summer! :D

Fact seems, bad decisions can be done in a heartbeat, and it takes a lot of time and effort to iteratively leave it behind. I estimate that ratio to be somewhere in the 1/1'000'000 - 1/10'000 range and that's where I have some actual hope in LLMs to bring that number closer to 1!

My newest revelation to unwanted walls: If part of your code doesn't need to have a new release-(or life-)cycle, just don't put it in a new repository. The scaling advantages of microservices can be achieved with just a single repository.

By @malux85 - 4 months
Perfect solution doesn’t exist in reality.

All ideals are surrounded by a moat of tolerance.

How do you eat an elephant? One bite at a time.

Leave it better than you found it.

By @ChrisMarshallNY - 4 months
I'm in the middle of a full rewrite of a backend statistic viewer app. Coming along well. I'll probably have it ready to go, in a couple more days.

It's not crucial, but I just published a new tutorial[0], and, like all my tutorials, it taught me more than it would teach others.

I just like to actually implement stuff that I learn. It helps to "solidify" it, in my mind.

In the app we released, early last year, I worked on it for about four years, in all.

At about the two-year mark, it was almost "ship-ready," but then, I tested it against absurdly large datasets, and it fell down hard.

So I rewrote it from the ground up. Took a while, but we didn't have ship pressure. In the end, it worked beautifully. People really seem to like it.

But I would probably recommend against doing that, with most projects.

WFM, YMMV

[0] https://littlegreenviper.com/series/swiftui-charts-gestures/

By @vouaobrasil - 4 months
> There are probably edge cases this code solves that we don’t remember.

Oh yeah, I agree with that. I wrote a program for work recently that probably should have been object-oriented. It would have been nice for it to be, because it's a bit of a mess now. But it works flawlessly and in truth it doesn't need any real feature upgrades now that it's done. So, I decided to keep it as it is.

By @hollywood_court - 4 months
I bought my ex-wife one and she loves that thing.