Build Only What You Need — Part 1 — Development

Dan Edwards
6 min readJan 31, 2023

The initial drive for this article was the continued observation that many teams are still delivering features which do not provide client value. Whilst this problem originates at the product level, it is also prevalent at the development level.

Why do we embark on these follies? This article takes a look at the reasons why we do this and how it can be prevented during the development phase. A follow up post will take a look at the product side of this issue.

Code Only What You Need

There’s a strong guideline that when writing code, you should write the minimum amount of code to solve the problem, nothing more, nothing less. Yet frequently, we encounter code which has been extended beyond what is necessary. What’s the issue here? This is additional functionality that we’ve got for free, so what is there not to like?

The hidden costs of ‘free’

So we have additional functionality, but what did it actually cost? Time is the first answer that springs to mind, time is easier to quantify than the underlying financial cost. It takes a developer time to write code, and the free functionality that has been added is code that the developer has taken time to create. Presumably this code is accompanied by a suite of tests capturing the additional functionality. There may also be a requirement to document this functionality, particularly if the functionality is exposed to others. Documentation is likely required, and documentation also takes time.

Now we have exposed functionality which the QA team will also want to take an interest in — assuming QA are made aware of the bonus behaviour. The involvement of QA results in the production of automated tests, again, the cost is time. But wait, the QA engineers may not be able to test this functionality immediately as it is bonus behaviour, and the behaviour has not been defined. The QA engineers must first establish what that expected behaviour is, before they can create the automated tests and validate the functionality. A conversation with the developer is all that is required to determine the behaviour, the behaviour is run by the Product representative who laps up this bonus functionality and everyone moves forward. But things take a turn for the worse when the diligent QA engineer notices that this behaviour is not consistent with other similar functionality the team defined previously. The developer is looking at doing some rework to bring this bonus functionality inline.

After a bit of to and fro between engineers the functionality is now clarified. Alas, as is always the way with these things, QA have found some bugs in the bonus code so a couple of dev/test cycles are burned on these fixes. Finally the code is corrected, tested, and deployed, but that is not the end of the costs

Abandon ship?

Sometime later a new use case lands, a genuine need for specified functionality. Unfortunately this new functionality is in the same area as our previous bonus behaviour, and it’s not a pretty picture. The unspecified feature’s implementation creates complications for the new functionality which is going to require a significant rework, which results in yet more time. Maybe now is the time to jettison the bonus behaviour we shouldn’t have added earlier, can we do that? No, it turns out that someone is now dependent on this functionality. The functionality has been exposed, so now we’re committed to supporting it until we can gracefully deprecate it…. On reflection, this has cost us a lot of time, and perhaps it wasn’t the free functionality we initially thought it might be.

Are we doing this? How are we doing this?

The creeping costs and complexities highlighted above are representative of many real life occurrences of unrequired functionality.

The larger the change the higher the costs will be, but the impact can be seen with all sizes of change

  • Sorting and filtering options on a query
  • Over engineering, ie providing the paging of responses when a limit of 10 items would suffice
  • Providing alternative retrieval methods
  • Additional input methods…
  • ‘Wouldn’t it be neat if’

All of these are a combination of inventing use cases and failing to keep things simple. Achieving what is needed with as little code as possible. Iterate and improve where needed.

Why did this happen?

In a nutshell, enthusiasm and a keenness to help, and possibly the presence of a ‘hero’ in the engineering team

Adding unnecessary behaviour is not a malicious activity. It’s rare that this would occur due to a rogue engineer who just disagrees with the decided course of action (though not unknown)

Keep things simple, do not invent use cases, keep the solution simple. Ie Do we need to provide the illusion of unbounded input in a request, when in reality users never appear to exceed 10 items?

Anecdotal evidence suggests that the majority of scope creep happens based on short term thinking and being in the code.

> ‘I’m It the code now, it will only take 5 minutes’

Even a ‘5 minute feature’ carries all the risks highlighted above; additional code, additional complexity, additional testing, unnecessary code, maintenance, support. What initially appears to have a small cost, 5 minutes of dev, can easily balloon into a high cost.

By adding the additional code and exposing more functionality you are taking on more risk.

Are you making a call that the current requirements are insufficient? That the minimum work identified will not solve the problem? Those cases are valid to flag, but you should have the team buy into the need for this change.

Or are you making a call that the users will demand this functionality despite the problem already having a clear resolution? This is a risk, risks should be minimised and avoided where possible. Unnecessary code is a risk that can be avoided.

Where to stop this?

Not creating the unnecessary functionality in the first instance is clearly the best situation to be in, but if discovered soon enough, removal should be the path.

Beware of the sunken cost fallacy trap, ‘The code has already been written’.

If this undefined functionality is discovered too late then mitigation activity may be your only option.

There are opportunities to help us catch unexpected behaviour

  • Acceptance Criteria
  • Team — Do not settle for poorly worded or vague acceptance criteria, challenge the ACs until you are all clear on the intent.
  • Developer — do not implement beyond what is needed/specified in the acceptance criteria
  • Standups — during discussion of work in progress
  • MR/PR — a second pair of eyes
  • QA analysis — QA seeing that functionality extends beyond what was proposed
  • QA discovering issues — If not caught by QA analysis, bugs are more visible to the wider team

What to do upon detection?

The cost of detecting bonus code increases the later it is detected. Once the code is released, removing the code becomes a costly exercise.

  • Discovery prior to deployment — If discussions on the perceived need do not resolve in favour of the extra functionality, then the code should be removed.
  • Discovery in the deployment — If the behaviour has not been released, then this could be your last opportunity to remove the code.
  • Discovery in Release — Removal of unwanted functionality — deprecation, this can be a costly exercise and may impact clients

Summing up

Delivering the minimum is the fastest way to validate our decisions. If we gamble on unnecessary hypotheses, we not only incur immediate penalties in the form of time, but the additional complexity costs us time later, resulting in extra effort required for test, maintenance, and support. By preventing the introduction of functionality beyond what is needed we provide ourselves with the fastest learning opportunities, which will take us to a cleaner, simpler, and more rewarding outcome.

Want to read more?

Head over to Lydtech Consulting to read this article and many others on interesting areas of software development.

--

--