I recently stumbled upon Tyler Neely's excellent talk about throwing away code at the end of the day, only to rewrite the same functionality the next day. The talk is only 10 minutes long but raises a lot of interesting points. I'm going to focus on the core idea here, which Tyler Neely attributes to Joe Armstrong:
Throw away anything you can't finish in a day.
In other words, whenever a program or piece of code turns out to be too hard to write in a single day, throw it away at the end of the day and start afresh the next day.
At first the idea almost sounded like a joke, especially since Joe Armstrong is known for some unconventional opinions. But I think Tyler Neely does a great job explaining why the idea has surprisingly deep implications. I'm not going to paraphrase his talk here (go watch it, it's really good), but instead want to summarize my experience toying with this approach over the past week.
I originally decided to try it out in the context of a small programming language that I'm writing, because I had hit a bit of coder's block trying to get a macro system based on explicit bindings to work. Coder's block is one of the more obvious motivations for daily rewrites mentioned in the talk: Instead of trying to fix code that becomes harder and harder to understand with every “fix”, you put in the work for one day, then throw it all away and start with a clean slate the next day, which will often lead to a simpler architecture.
Over the past week, I've rewritten most of the parts of my (admittedly small) toy language several times: The scanner and parser were each rewritten 4 times from scratch, the translation stage into something resembling lambda calculus was rewritten once (because I was busy trying to get the scanner and parser right). Here are some early observations:
Apart from being a very good antidote to coder's block, the approach of throwing away all code at the end of the day was also surprisingly effective when it came to code quality: I had expected the code to improve over the course of rewriting it (after all, “plan to throw one away” is a thing, but then again so is the “second system effect”), but I was still surprised by how much better and simpler it was. Both the scanner and parser ended up being half the lines of code I had started with, while being conceptually simpler and cleaner. Of course this happens often in a rewrite, but I was struck by how effortless it felt, which I think has a lot to do with doing it immediately the next day, after getting some sleep and thinking about the problem, not after weeks or months of other work.
Throwing away code deliberately forced me to think about the same problems repeatedly and approach the design of the code iteratively. This didn't only lead to better code, but also felt satisfying, like building up some muscles through repeated exercise. I could see that I was getting better at the same task, which felt a lot more fulfilling than bashing my head against the wall trying to come up with a solution when I didn't have a clear path in mind.
In a way, repeatedly solving problems that I knew I could solve was exactly the point: It is rare in programming to build the same product repeatedly, because code, by its very nature, can be reproduced endlessly, so why build something again when you already have the code for it? But this view robs us of the joy of crafting things over and over again, seeing them get better every time.
I noticed another pattern that seems obvious in retrospect, but definitely wasn't when I first started throwing away code at the end of the day: After the first couple of rewrites, I very consciously thought about which features were really, really essential, because including them didn't just mean that I had to write the code once, but every single day from now on.
This sounds sisyphean, and in a way it is, because following the idea of throwing away code to its ultimate conclusion condemns us to roll every single feature up a hill, every day, just to see it roll down again. But what I found surprising is that this is not at all a cause for despair, but actually quite freeing. To borrow from Albert Camus: One must imagine Sisyphus happy!
Having to rewrite a feature every day meant that each feature had to pull its weight and that feature creep was much much harder to justify. I felt as if I was trying to chop my way through the undergrowth, getting farther each day. But since I started my journey always at the same base camp, I was forced to think about how much I really wanted to carry. I had to travel light, which forced me to find a simpler solution.
I'd be the first to admit that I interpreted Joe Armstrong's / Tyler Neely's idea quite dogmatically. It is important to point out that Joe Armstrong qualifies the approach by saying that you should throw away all code that you can't finish in a day. But when is code considered finished? Is code ever finished?
I decided to apply the idea more dogmatically than it was perhaps meant precisely because I wanted to avoid giving myself any leeway to let complexity creep back in again through the backdoor. Should I have considered the scanner finished after the first day? The parser after the second day? I could have, but this would have robbed me of an opportunity to get to a simpler and cleaner solution.
If you treat your codebase as a collection of tiny components that you can build quickly and then call finished, you won't throw away much code and basically end up with a traditional accumulative approach. So what should your component boundaries be? This is obviously a judgement call, I think Tyler Neely for example considers everything that deserves its own Rust crate “finished”, which seems like a good approach (and more practical than mine).
Throwing away code at the end of the day can be a powerful tool to enforce simplicity, but it's no silver bullet. A lot of software probably doesn't need a radically simple approach, especially not early in its life when it might be more important to exploratively find a use case and the quality of the design is of lesser concern.
But I think that daily rewrites can work well in a context such as VPRI's attempt to build a whole computing system in just 20.000 lines of code, orders of magnitudes smaller than existing designs. This is perhaps not something that has an immediate business case, but our world needs more attempts to deeply understand and simplify our foundations, instead of piling on more and more features, hoping that the tower won't come crashing down. To borrow from another philosopher:
This spirit is different from the one which informs the vast stream of European and American civilization in which all of us stand. That spirit expresses itself in an onwards movement, in building ever larger and more complicated structures; the other in striving after clarity and perspicuity in no matter what structure. The first tries to grasp the world by way of its periphery—in its variety; the second at its centre—in its essence. And so the first adds one construction to another, moving on and up, as it were, from one stage to the next, while the other remains where it is and what it tries to grasp is always the same.
— Ludwig Wittgenstein, Philosophical Remarks, Foreword