Quite early in my career, a colleague of mine had a very different approach to programming than me. No matter the difficulty of the problem, he was always able to solve a problem very quickly. From my perspective, the code was rather ugly - it was inconsistent, did not follow many “best practices” (whatever that meant to me at the time), was not maintainable or very readable. But it did the job. I saw myself as a counterbalance to this - I wanted to write code that would follow best practices, could be considered high-quality and could be something that I was proud of. But it always took me way, way longer to create such code. I was frustrated by that and got used to always being late with delivering, thinking it is the inevitable price of quality - I wasn’t capable of imagining I could create quality code fast.

Now, years later, armed with more insight into what was happening at the heart of this difference, I think there were two main aspects at play.

One: as I considered it my duty to bring quality into the team, I thought it was totally warranted to invest any amount of time into the quality, technical discussions, contemplating high-level ideas and refactoring “bad” code. While this helped me immensely to build a solid knowledge foundation, pick up an eye for quality and start a lifelong passion for writing code that follows best practices, it was not healthy for the business. Had I a mentor in that time, I think I could have learnt way earlier about timeboxing, the power of good tradeoffs and fast iterations - without a teacher, I simply cost the company more than I brought to the table.

Two: after reading John Ousterhout’s A Philosophy of Software Design, I understood that my colleague could be labeled as a tactical programmer, delivering very fast but without a strategy:

In the tactical approach, your main focus is to get something working, such as a new feature or a bug fix. At first glance this seems totally reasonable: what could be more important than writing code that works? However, tactical programming makes it nearly impossible to produce a good system design. The problem with tactical programming is that it is short-sighted. If you’re programming tactically, you’re trying to finish a task as quickly as possible. Perhaps you have a hard deadline. As a result, planning for the future isn’t a priority. You don’t spend much time looking for the best design; you just want to get something working soon. You tell yourself that it’s OK to add a bit of complexity or introduce a small kludge or two, if that allows the current task to be completed more quickly. This is how systems become complicated.

I, on the other hand, intuitively strived for a strategic approach. That however was much more costly, because it was always very hard for me to come up with all the abstractions up front. This took me all the longer because of how my brain works - while some people can start with coding pretty much at the same time as they learn about the very first aspect of the domain problem, I always need to build an understanding of the whole problem first, gaining for myself a much better strategic position but also a fundamental delay.

As insightful as these realizations were for me to understand my past, there is also a specific lesson for my future: On the topic of building an understanding of the problem and thus picking the right abstractions (as much as an abstraction can ever be considered “right”), I recently found a very simple approach which I pity I didn’t come up with myself much earlier. It’s called building a spike = writing some exploratory code in a very tactical fashion to grasp enough understanding of the domain problem to be able to then build quality code in a second iteration. I don’t have enough experience with spikes yet to decide whether spike should already be production-ready - intuitively, I think not, but maybe under a feature flag it could already bring some very early validation (see update below). Building the spike can be done in a collaboration with a product manager, especially in a situation when the domain problem is not fully understood even from a product side. A very important part of leveraging a spike successfully is an agreement of the whole team upon what a spike is and that the subsequent iteration of rewriting the code to a quality one is not something optional. I find this very important - if the engineer does not feel sure that they will really get an opportunity to rewrite the code into a good shape, they will try to account for the possibility of the spike being the final code by actually trying to write it strategically, which denies the purpose of the spike in the first place.

I think a spike could be a way to take the benefits of tactical programming and use them to improve your strategy faster.

Update 2024-01-03: I forgot to explicitly mention that within a spike, no tests are written. So even if you mostly use TDD when developing, you probably won’t in a spike, because the code is not meant to be reusable - not even in tests. That also touches the aforementioned production readiness of the spike - you’d need to write some tests first, untested code should not go into production, period.