One challenge with all software projects is managing growth over time. The last two teams I've joined (BBC News, The Guardian) have viewed their current platforms, both of around ~4 years old with a few dozen weekly contributors, as monoliths - a byword for something unsustainable, unmanageable, and ultimately something that is slowing the progress of the business.
But monoliths don't start out as monoliths.
A software team who builds software that is obvious to improve, simple to introduce new features, easy to do things quickly is seen to be doing a good job, and, ironically, it's only through the daily addition of code layered upon code layered upon code do things begin to seem unmanageable after a while.
Avoiding this is partly to do with even-shifting sands of good architectural practice, tools and so on, but more so, I believe, to do with the mentality of the project team.
204 additions, 2 deletions
Generally speaking, I see things added to our project every day that fall in to these three categories:-
- Things that are added to the project that might yield some long-term benefit (we hope).
- Things that are only needed in the short-term.
- Things that the project shouldn't be doing.
The problem with the first thing is that we don't know if they really will provide a durable benefit. At the point of being added we almost always think they will, but I expect most software is bloated by unused or unloved features, and once the initial excitement has passed things can get ignored.
The problem with the second thing is that it's very easy to forget to remove them once you are done. When looking at our project in investigating this article I found nine features and AB tests that we no longer needed, but nobody had a diary appointment to remove them, and so the code rumbles on, growing by the day.
The last is often a matter of perspective. But a project that does not defend its ground will end up bigger, more complicated, less focussed. When we add something to the code and later realise it was a mistake it needs removing.
What is needed in each of these cases is a regular, structured review of the value of the feature and it's place in the project.
Dead man's switch
What if we could implement a dead man's switch whereby the feature in question could remove itself from operation if nobody was paying attention or it became unowned and unloved?
Our switch model has an
description, a default
state, and now a
val ReleaseMessageSwitch = Switch( "release-message", "If this is switched on users will be messaged ...", safeState = Off sellByDate = new DateMidnight(2014, 4, 1) )
def isSwitchedOn: Boolean = delegate.isSwitchedOn && new DateMidnight().isBefore(sellByDate)
If a feature has expired, two things happen.
- The feature is disabled (I.e. it's state is always false, even if it's switched on), which means the feature will disappear from public view.
- The unit tests break, which means we can't build a new application.
At this point someone should remove it or seek justification as to why it should live on.
It's an opportunity to talk about the feature with the team.
- Do we still need it?
- Does it still perform well? (Or, what could we do to improve it?)
- Have we had problems with it?
- ... and so on.
With these simple additions the application is now animated. Each thing we add has a programmed life span and needs humans, vigilance, and a small amount of effort/investment in order to stay alive.
It asks people to think how long a feature should last in production, rather than an indefinite default.
It's a bit unfair to have something just turn itself off without notice, so we've implemented an early warning system as part of our daily routine.
The dashboard the hovers above the team now displays features with an expiry date of less than seven days.
As with anything red on the dashboard we take note and organise among ourselves to fix it.