Best code in available time
June 4, 2018 in Engineering philosophyOur job as software engineers is to bring other people’s dreams into the real world through a narrow time window. Our responsibility as software architects is to make sure they will fit.
A programmer has to reconcile two opposing sides in their mind continually. One side is getting the job done as soon as possible - copy-pasting, hacks, spaghetti code are fair game. The other side is producing the most elegant solution - with disregard for time constraints. The programmer has to somehow explain to oneself, why does someone who favors clean coding practices and beautiful code, still regularly produce kludge, hacks and tech debt.
I often hear dissatisfaction with not being able to produce a perfect solution in the time available. This is perhaps the most common programmer complaint. (Second most common? Dealing with prior imperfect solutions.)
Of course, it is not always possible to do the job perfectly, or even decently, in the arbitrary time constraints of the real world. There is none and never could be a methodology, approach, or practice that grants you this ability.
But let’s talk about that dissatisfaction though.
I think the dissatisfaction comes putting the perfect code as the ideal, and time as this external factor that works against us. Our ascension to the perfect code is always cut short by time. Sure we’d do that great refactoring and cover all those edge cases, but the PM is forcing us to move forward, to new things, leaving the code with tech debt and us with a feeling of incompetence.
The problem with external factors is that we don’t have any control over them. And while it may be worthy to put adequate estimates on tasks, or argue for some leeway in the schedule, there will still never be enough time to make the code perfect. By placing the blame on time, we lose any chance of succeeding.
I suggest you consider time as an input factor, and to set the best code in the available time as your goal. This will not dramatically improve your productivity, and of course, it won’t help you deliver perfect code. However, I have found that this goal enables you to be more efficient, ship better code, and - maybe most importantly - be satisfied and proud of it.
Cutting corners
Making best code in the available time requires cutting corners. Treat cutting corners not as a sin, but as a way to focus on the essential parts. Don’t think of a feature as something that can be “done completely,” given unlimited time. There is no such thing. There are only approximations. Your job is to find the optimal approximation, somewhere between spaghetti code and perfection.
- Develop a sense of what to test and what to trust. Don’t aim for 100% test coverage. Some code is cumbersome to test but can be verified with a human review. Some code changes infrequently and tests won’t bring lasting benefit. And some code needs to be rigorously tested.
- Develop a sense of bug tolerance. Intuit what bugs would be critical and what tolerable - before you even program the bugs in. Disregard edge cases that are unlikely.
- Plan refactoring ahead of time and sacrifice features or robustness for refactoring. This pays off in the long run by having a more manageable codebase to work with. Counter-intuitively, spending time on refactoring saves you time on future features - and leaves more time for future refactoring!
- Refactor for pliability, not for perfection. The goal of refactoring is to make the system easier to understand and work with.
- Evaluate coding techniques not by how elegant and perfect they are, but how they save your time - now and in the future. Future is usually far more critical than right now, against human intuition. Writing the purest functional code might be as elegant as haiku, but what good is it if it makes simple tasks twice as slow?
- Learn to use automation to make you work faster. Automation may not produce perfect results, but you can’t beat it on efficiency. Code formatters are a great example. Compile-time type-checking instead of unit tests is another.
- Use feedback to adjust your priorities. Own up to your work. If you are cutting the right corners, users should not care. Engineers, QA and users value bugs differently. Use every bit of feedback to learn their point of view.
- Be proud of your ability to deliver. Accept imperfection. Your value as an engineer is consistently shipping features. Don’t dwell on the glory days of being a student with no responsibilities, when you could polish your never released game engine to sheer architectural brilliance.
These concepts don’t make you into a lazy hack. In fact, they are practiced and valued by engineers in other fields. Those engineers are lucky to exist in the physical world, where perfection is not possible even in theory. Programmers, on the other hand, seem to work in a pure mathematical dimension.
But remember, we are still engineers, not mathematicians, and our jobs bring tangible results in real time. Aiming for best code in the available time will deliver better results than aiming for perfect code and stopping prematurely.
Liked the post? Treat me to a coffee