When you build software, it’s easy to get wrapped up in the wrong details or to get ahead of yourself.
Programmers sometimes optimize code prematurely. Or we apply unnecessary patterns, structures and frameworks. This makes our work more complex than it needs to be.
Newer programmers seem to fall into this trap more, but experienced people also get things mixed up occasionally. I certainly do. So: one thing at a time.
First, just make it work
This step is mandatory.
Get something basic working first, with the least amount of moving parts. We sometimes call this a “walking skeleton”: get enough of the system or feature working to understand the problem and cover the most basic requirements.
Cover the absolute minimum functionality needed. Don’t go down rabbit holes, don’t bullet-proof it. Just get it up and running. Do write clean, simple and straight forward code — but don’t worry about all the best practices yet.
It’s easier to remove duplication in your code after you have it working, rather than trying to predict up front where you might need a reusable function.
If you are building for someone else, show your solution off as early as possible. This lets you course-correct, in case you have misunderstood or missed a basic premise.
At this point, only pull in the tools, patterns and frameworks that solve the basic problem.
Then, make it right (enough)
This step is also mandatory.
This is where you focus on code quality, refactoring, graceful error handling, edge cases, network issues, and so on. You want to make your code robust, flexible enough to handle likely upcoming features, and easy for both others and your future self to maintain. And since you’ve solved the basic problem, you know how much time you have left to improve your solution.
If you are building it for someone else, show and discuss your solution with them along the way. This helps avoid “gold-plating”, where you spend time handling scenarios and edge cases that won’t actually be a problem.
If you don’t have time to make your work as maintainable or robust as you’d like, make this very clear to everyone involved. Document such technical debt explictly in your code: “This will only work up to point X. Refactor if we need to handle Y. Will not work with scenario Z.”
At this point, only pull in the tools, patterns and frameworks that make the system more robust and maintainable.
Finally, (consider) making it fast
This step may be optional. Caution: the previous step made your code prettier. This current step can make it uglier again.
When you start to optimize for speed, your codebase often takes on more complexity and becomes harder to understand and work with. A highly optimized loop is harder to read. A cache adds complexity and moving parts.
Many software systems only need to work, they don’t require any special tuning for performance. Consider the usage scenario, the target audience, and do performance testing to figure out if more performance is necessary.
I was once on a web app project where there was only a few dozen end users, yet the tech lead decided to use clustering to “make sure it’s fast enough”. This added uneccessary complexity and more moving parts.
At this point, only pull in tools, patterns and frameworks if they actually make the system more performant.
One step at a time.