Tactical or Strategic? Why not both?14 Nov 2023 · 📖 in 4 minutes There's always a trade-off somewhere
In most engineering discussions there's likely going to be a point where there's a trade-off to be made, do we solve this now, short term, or do we wait for a more thorough solution to be implemented.
I believe it's always a good idea to outline all the options available to us before making a decision like this and that requires time to look at the problem and come up with different approaches.
Not all businesses can afford engineering to take the time needed to do the thorough work, deadline pressure is real and often leads to finding a shorter term tactical solution and adding the strategic options onto an ever-growing list of tech debt.
What might this look like?
Imagine an Order processing service which handles orders and deals with making sure the payments are being processed - something you might find in most businesses (though if a business is small enough they may rely on a third party for this but the idea is still the same).
undefined property that an engineer assumed would defined at runtime.
activeOrder is being read for an
id and assigned to another variable
const orderNumber = orderBook.activeOrder.id;
On the face of it this is pretty straightforward code and would likely pass code review without much query. However what happens when the
Because the way we're reading this
id parameter we're assuming that
activeOrder is always present and that might be a fair assumption to make but there's no guarantees without more robust checking and unit testing.
undefined case the code will error with
TypeError: undefined is not an object and this could cause real loss to a business.
It's 2am on a Saturday and you've been paged because a recent deployment has changed some logic which has lead to the
activeOrder being removed.
A short term 'tactical' fix would be to use optional chaining on the
const orderNumber = orderBook.activeOrder?.id;
In this case we at least expect
undefined for the
activeOrder and depending on how
orderNumber is being used elsewhere we'd have to then write additional logic.
Similarly a unit test could be written to cover this negative case in the future and harden our code.
However, throwing in a unit test and optional chaining is a reactive fix to an issue and solves only the issue in front of you.
A more strategic fix could be to add
TypeScript to that file and its dependencies in order to protect against this type of issue in the future.
Is the trade-off worth it?
Though the above example is fairly contrived I have seen it play out a number of times in production code. Eventually the short term fixes pile up until there's a web of spaghetti that no-one really fully understands and folks joining the team have a hard time ramping up.
With that in mind, no, it's almost never worth it and creates more work in the long run.
Creating a ticket, TODO or note on the specific issue detailing the tactical / strategic options and making sure everyone is aware of what was done, is the first step. The next is doing the follow up work.
In the case above the
TypeScriptmigration might be a huge undertaking (and in this specific case there's help) but it will always pay dividends to prioritise the strategic fix.
Getting buy-in for the follow up work might take some convincing, for production issues a good postmortem can help align everyone and get everyone thinking about the problems (even better if you can demonstrate the actual cost to the business). A good write-up of the ongoing issues, grouping similar cases will add weight to the idea that a more strategic fix is needed.
After a certain point though this becomes less of an issue of doing a strategic fix and more of one on coping with tech debt... which is a topic for another day.First appeared on Trusty Interior, last update 14 Nov 2023