Just enough perfection

Last week an engineer on my team reviewed a pull-request of mine and suggested a "full review of the business logic of our app". I was taken aback and it got me thinking... Do we really need to do this? Won't it slow us down massively? What if we waste a whole bunch of time?

I thought about the nature of start ups and the conflict between sprinting at full velocity vs incurring irreversible technical debt. I've noted down my thoughts and feelings on this in the following post.

Fair warning. This is my personal opinions on how start-ups should prioritize architectural and technical debt vs innovation and agility.

The nature of product development in start-ups

In start-ups, it's usual to change fundamental product ideas at a moments notice. New ideas come and go frequently and it's very important for engineers to be able to accommodate change as quickly and effectively as possible. As a result, engineers, designers and product managers are faced with the challenge of balancing stability, performance and functionality in the face of being first to market. However, ultimately what matters most is whether the product idea is good, fits a need in the market, addresses that need in a timely manner and is just good enough to avoid churn and maintain growth. Often this results in codebases that are riddled with hacks, technical debt and "wtf" moments.

What can engineers to do help?

Prevention is better than cure

As engineers working on the product, there's a constant need to discern what's the best solution immediately vs what's going to remain the best solution in a year, two or 10 from now. In start-ups that have a largely unproven product, it's hardly worth considering whether the code written now will remain the same in more than a few years time, as it's incredibly unlikely that the product will remain the same, let alone the code. However, it's highly likely that the code written today will be chopped up, re-organised and re-written and that is precisely what engineers should be focused on optimising when architecting the application. Often this results in taking pragmatic steps early-on in the development of the application and from then on, thinking in terms of what's best in the short term. In my opinion, this boils down to a few key areas of focus:

  • Working from good foundations (languages, frameworks, design patterns)
  • Code decoupling, clear separation of concerns and well defined protocols between services (typed DTOs, effective project structure, adherence to DRY and YAGNI where appropriate)
  • Automating monotonous tasks such as deployment
  • Effective automated exception tracking
  • Reliance on effective tooling (type-safe languages, automated testing, continuous integration and delivery)
  • Documentation of architecture and guiding principles (good for hiring and onboarding too!)

Of course, it's much easier to implement these practices in greenfield projects so what about existing technical debt?

Technical debt

It's a fine art to balance spending time on tackling existing technical debt and stability improvements vs working on adding or improving functionality for the user. I typically ask myself the following questions when I evaluate technical debt in the context of a fast paced start-up:

Does addressing this technical debt help ship improvements and features more quickly and effectively in the future?

Any technical debt suggestion that will facilitate the team's effectiveness and efficiency is a welcome suggestion in start-ups as it directly impacts the companies ability to iteratate. However, the time in which it takes to implement the suggestion should be weighed up against the time savings that the suggestion will enable. As an extreme example, if the suggestion is to change a piece of fundamental architecture or the language the application is written in, it can take many months if not years to achieve, at which point it's worth questioning whether the benefits of stopping any improvements to the product during this time is worth the risk. If this suggestion be isolated to one small and fairly risk-free area of the application to test it's feasibility, it might be worth exploring.

Does fixing this technical debt enable making changes to the codebase with more trust and safety?

Time is a luxury in fast paced start-ups, and it's often bugs and technical debt that is deprioritised (often rightly) in favour of features and product improvements. Therefore, it's worth investing some time up-front to ensure that making changes to the product is as risk-free as possible, as there is often little time to go back and fix things later. When a suggestion for addressing technical debt comes along that directly impacts safety in the codebase, it's definitely worth considering.

If your fortunate enough to be working on a greenfield project, it's much easier to have a high impact in this area early-on. For example choosing a language that has explicit types, choosing a framework that has a lot of community support and adhering to best practises such as DRY and YAGNI.

What will happen if the technical debt isn't addressed now?

Likened to monetary debt, there is an intrinsic property of technical debt that the longer it takes to address, the larger the debt is to repay. This is the snowball effect and it applies to a particular class of technical debt that infects other areas of the codebase and gets worse over time.

This class of technical debt can become extremely costly, if not impossible to address beyond a certain time frame. Examples of this type of debt include the prolonged use of esoteric or depreciated frameworks / programming languages, lack of adequate acceptance testing etc.

Conversely, if the technical debt is too minor in severity and isn't contagious then it's likely not important enough to warrant spending time on. Examples of this type of debt include code-style choice (semicolons...?), minor inconsistencies, naming conventions etc. It's hardly worth spending time discussing these stylistic choices.

The technical debt that resides between these two extremes is the type of technical debt that is worth investigating as it's small enough to be addressed in a timely manner and the benefits are typically immediate.

How isolated is the technical debt? Will it eventually disappear?

Depending on the architecture of the codebase, the maturity of the product and the goals of the business, it's entirely possible that the technical debt may resolve itself by simply getting removed from the product at some point in the future. For example, what would be the point in solving some technical debt in the username/password authentication journey if this particular feature will be replaced by social sign-in in the next sprint?

How much time will it take to implement before seeing a benefit?

If implementing a suggestion will slow down the business, or cost too much to implement then it's simply not worth doing. Examples of this type of debt include application re-writes, changing programming language and/or framework where many months if not years of engineering effort are required before seeing any benefit.

Conversely, if the suggestion is fairly trivial and the benefits immediate, then it's likely worth doing. Examples of this type of debt include things like continuous integration / delivery that are often fairly easy to set up and save many hours of time in the long run.

Does this suggestion really help?

Yuu might agree that the suggestion is worthwhile, but is it really going to make a difference to the team or the product? This is where you have to leave all egos at the door and evaulate the priority according to the needs of the business and the wider team.

Wrapping up

Hopefully these suggestions will help you when you're struggling to find priority in a fast paced start-up. To summarise, being pragmatic is key, and working on improvements that help facilitate quick, safe and effective product improvements are worth while addressing, providing they don't slow down feature development too much!