Tactical vs Strategic Programming

3 min read

Good software is a deliberate act. It's no accident that we've developed dozens of tools in the past few decades to automate and standardize the software development process. But tools and practices can only get us so far. Great software, as computer science researcher and professor John Ousterhout suggests, requires an investment mindset that we take on before approaching a programming problem. Most companies encourage us to ship features fast. "Write, Ship, Repeat" is the motto. And while we have linters, formatters, a couple of test cases, and now, copilots to help us, it seems like enough to create quality software. However, if we really need to craft our software with great design, we must opt for the strategic mindset.

tactical / ˈtak-ti-kəl /
made or carried out with only a limited or immediate end in view

strategic / strəˈtiːdʒɪk /
designed or planned to serve a particular purpose, gaining a long-term advantage

Tactical Programming

Software, irrespective of its size, must stay simple. The worst thing to happen to a piece of software is to get complicated. And one thing that could be said to be single-handedly responsible for introducing the complications in software systems is Tactical Programming. Tactical Programming is a mindset we adopt before approaching a programming problem. The problem with it is that it's short-sighted. When programming tactically, we tend to "get things done" quickly. It's hard to think of designing the system when the tactical mode is on. We always think of "returning to it later"; maybe we leave a "// TODO:," and the patchwork continues. It's just a matter of time that these patches, each adding a tiny bit of complexity to the system, turn it into a giant mess.

Startups, or the organizations who intentionally decide to move fast, are hit really strong with this problem. It's easy to think that you can move fast now, and when you have more users and more funds, you can come back with a larger team, with a couple of developers working solely on designing the system to make it more robust. That never happens. A few hundred commits to your repository, with each adding code written with no intention of the design, leave it in a state where it's not fixable. That's the reason rewrites are more common than refactoring.

We all have that developer on the team who'd write code and ship features faster than others. For the management, he is a "10x Developer", teammates think he's a Code Ninja, and he sees himself as no less than a hero. Before you start feeling jealous, let me tell you I'm talking about the developer who ships feature light speed fast but leave a spaghetti mess in code for the teammates to clean. Such developers who take tactical programming to extremes are called tactical tornadoes. The fact, however, is that we all have been "that developer" at some point in our careers. And we still are on some days, if not all.

Strategic Programming

The first step to becoming a great developer is approaching software development as a design task. We all spend significant time working on a single project before moving on to the next. And during that time, each job involves extending the current system. Therefore, instead of taking the fastest path to complete the task, it's essential to deploy a long-term mindset and use some time to refactor and design the system each time we add new functionality or fix bugs. This is Strategic Programming.

Ben Orenstein's talk from Ruby Conf 2012, "Refactoring from Good to Great," is a perfect recommendation in this context because it teaches us how to approach refactoring with every new task. We must be proactive in exploring the design of new classes, functions, and modules. Instead of implementing the solutions that first come to mind, we must look for alternative patterns and pick the best one. Although it doesn't guarantee a perfect design, it makes the code simple enough to deal with at any point in the future.

Strategic Programming is not a single rewrite or refactoring every few weeks. Instead, it's when we continually make improvements to the codebase. As a result, we reduce the complexity of our software system with each task, which is the opposite of what we do in Tactical Programming, where we add complexity each time we push a commit to the codebase.

Why tests can't save us from a complex software system

Tests test the function and behavior of our application and/or system. They are to ensure that the system behaves as it's supposed to. A codebase can have all tests passing and, at the same time, be equally complex to deal with. How complex or simple is a codebase is determined by its design. The better the design, the easier and more approachable it is for developers, both new and the ones familiar with it.

Don't just write code that works; design it!

Sources/Inspiration:

Refactoring from Good to Great — Ben Orenstein
A Philosophy of Software Design | Talks at Google — John Ousterhout
Creating Great Programmers with a Software Design Studio — John Ousterhout