I rewrote the parser today.

The old code rolled its own state machine across three files and depended on subtle ordering. You had to initialize the context before calling the tokenizer, and the tokenizer had to run before the resolver. If you got that wrong, the error was a null dereference three frames deep with no useful message. It worked. I shipped it. I moved on.

The new one walks the tokens once. It is half the size, runs faster, and I can hold it in my head.

Simpler was better.


I used to treat shipping as the finish line. It is not always that clean. You can ship something that still takes too much context to maintain. For me, finished means I can come back later and understand what is going on without rebuilding the whole problem in my head.

The first version is usually a working draft. It solves the problem, but it also carries assumptions and false starts from when I did not understand the problem yet. The next pass can usually be shorter and clearer.

I often used to stop after the first working version. I am trying to do that less.


The note I keep coming back to: ship the first version, then clean it up. You need the first version to learn what you are actually building. But shipping and being done are not always the same thing.

Done means I could explain it or hand it off. The code should reflect what I understand now, not just what I understood when I started.

That second pass is usually where the project starts to feel maintainable.