A Risky Way To Account For Changing Product Requirements
All developers have to think about the reality that product requirements change. This is especially true for early products where user research typically needs to last past the first few releases. I’ve started to realize that one way to maintain a lot of flexibility is to minimize logic on the backend and keep things as simple as possible.
An example of this would be: let’s say you have a system where your users have lists of things. You want to give them a way to move something from one list to another. You may have the backend provide an endpoint that makes this transfer. The alternative is to keep the backend simple and have just add and delete endpoints. To transfer an item, you would have the front end make one API request to add to one list and one request to delete from the other.
“BUT YOU ARE INTRODUCING A BUG!”
I sure am. That’s what makes this a little uncomfortable.
The bug here is that the operation is no longer atomic. Either adding or deleting could fail. In that situation, instead of moving a thing from one list to another, we could either be just deleting it from the original list or having the thing be in both lists. The odds of this are probably very small if your user base is in an area with great and consistent internet connections. But a low probability is not zero.
The case for deliberately introducing this bug is that it can significantly increase development speed in multiple ways.
The first is that you simply have to write less code. Presumably, the endpoints to add and delete things from lists would already exist. A transfer feature is zero new code written on the backend. Less code also means less tests need to be done on the backend and if/when a different bug appears, it is less likely to be caused by the backend. This reduces debugging time.
There are also fewer dependencies between the backend and frontend. Every piece of logic on the backend has to be accounted for in the front end. This isn’t necessarily because the code has a dependency, but the user experience is presumably going to have to account for the fact that the backend is doing something. This is less of an issue with a team of full stack engineers, but it does result in less coordination needed if an organization has separate frontend and backend teams.
Ultimately it also means lots of changes can happen on the front end to account for user feedback without having to touch backend code. It also opens up the possibility for running multiple front ends to try and experiment with different user experiences or branding. With a simplified backend, there is less of a concern that making a logic change for one frontend will break another. It is always easier to add complexity to a simple system than it is to change a complex system. Too often are developers faced with situations where users ultimately need something that they didn’t design the system for. That tends to happen more when we try to build the “perfect“ product before a single user sees it. A little imperfection deliberately introduced can make life much easier and development go much smoother.
There are some situations where this strategy is completely invalid. It’s fine to have this bug appear 1 out of 10,000 transfers between say user wish lists or todo lists. It is completely unacceptable to have this bug appear even once when transferring money between 2 user’s accounts. In the latter case, no amount of extra development speed is worth the cost of the bug.
Software development always requires some kind of trade off. We’ll often consider letting bugs slip through for increased development speed when necessary (e.g. pick 2 out of speed, quality, and features). Yet, deliberately introducing a bug feels like it crosses a red line. It is the same trade off though and I think it can make sense in a lot of situations.