Monday, June 6, 2011

Features Don't Freeze Themselves

How do we know when its time to stop adding new features to our software? When do we take off our creative hats and put on our cautious stability testing hats? Once we meet the functional requirements of the project, that's when. The term used to describe this dividing line between the chaos of development and the order of a finished product is feature freeze. Yep, features are literally frozen in time. They don't change or evolve from this point forward, until the release is out. After releasing, we've got a clean slate, more or less. Its time to start adding new features, and expanding on those previously released. So my question is this: is the feature freeze phase really that straightforward to define? By this I mean, can way really say with confidence, based on the project requirements, that we'll know ahead of time when we're going to stop making new code? I would argue not, as would most developers because impudent predictions are discouraged in software development. This problem is more pronounced when we're building things that have ambiguous requirements, things like frameworks.

The rationale behind feature freezes is self-explanatory for the most part. There has to be a point during development were we devote our efforts to quality assurance. Of course, we do this even during development too – modern approaches to software development involving test harnesses for each feature. Even this is limited, speaking to the system as a whole, how it will be used in the wild by real users? The incremental develop and test approach cannot cover all cases. So we have to stop development, and try our best to destroy our code, automating as much of the process as possible. We pick and prod and poke, systematically dismantling what we've built until we can't find any weak points. This is the best way to build reliable software. This is also the ideal scenario - not exactly reflective of what we face in reality.

Think about software requirements for a client project. When have you ever built software based on a single set of unchanging requirements? That's what I thought, never. Requirements change and so does everything else. By everything else, I mean the driving force behind the requirements. The client wants a new widget because it'll make a new department at their organization function more efficiently. They want a new social widget because it is the brand new thing the simply cannot live without. They want the colours slightly different, due to nothing more than their mood that day. Who knows why? The important thing to remember is that initial requirements are nothing more than a general prototype of what the finished product might look like. Requests for change usually don't arrive until well after it would have been convenient for us to implement.

You'd think this whole implement the requirements thing would be a little easier when building a product that solves a more general problem. For example, shopping cart software, income tax reporting software, image editing software. The list goes on – things that are general enough that more than a small group of people might find it useful. Maybe a better example is the Linux kernel. You'd think that solving a general problem would make the requirements a little more concise. The opposite is when building stuff based on a contract for a company. Usually a web application of one flavour or another. These have more niche things that need building. This is why you were hired to build them in the first place – to execute a precise vision. Consumer software on the other hand, usually doesn't cater to the specific problems of any one organization. And this is the challenging part when it comes to devising requirements for such a thing.

There is no such thing as a universal software product that will solve every problem for every person on the planet. Not even for a specific problem domain is this true. There will always be a deficiency, a missing feature, or maybe the user interface is just not that friendly. This is why alternatives exist for every kind of consumer software system. Linux isn't for everyone, nor Windows, nor Mac. Users have their own reasons for choosing one over the other. Different software that strives to solve the same problem excels in different aspects. Some operating systems are more user-friendly than others, and some are more secure, and some are used for the sake of familiarity, why change? Again, no software caters to everyone. That doesn't always stop us from trying though.

Programmers are perfectionists. Not only does the code need to look beautiful, it must work as expected, with no bugs. More than that, we also care about the usefulness of our software, more than some might think. Nobody wants to write code that doesn't help somebody solve a problem. This is considered a failure, and this is never fun, we've all been there before. Successful projects have at least a handful, maybe a couple hundred people using it on a daily process. Ideally, users recommend it and praise its usefulness and how they simply could not live without it. I know I have a list of a dozen or so tools I'd be lost without. This is always great to hear. However, we also hear about those who struggle with certain aspects of the system or outright stopped using it for some reason or another. Hearing these things about code you've sweated over are hard to ignore. Maybe the last feature freeze wasn't such a good idea after all, I mean, there are people out there disappointed with our product!

And so it begins, we've now got to start the task of expanding our system's capabilities to accommodate. Built to please. This is the cycle, add new features, expand existing features. Accommodation. The problem is, the cycle never ends, and what you're left with is usually something bloated that looks nothing like the initial vision held so long ago. How do we break the cycle?

Loose-coupling is part of the solution to the feature bloating problem. Large systems that have grown due to the sheer number of features it provides aren't necessarily loosely-coupled. Can you download and install a smaller version of the software? For example, some applications let you configure the pieces you'll need before installing and using it. This isn't possible if your system's components are tightly-coupled. However, even this approach might not suit all users because they may need these negated components down the road. Can they add the missing pieces? Or do they need to repeat the entire download and install process? I think the only real solution is to develop independently installable components.

If your software can be broken down into individual components, there aren't so many dependencies that force features on users that don't need them. Building independently installable components, and designing loosely-coupled code, however, are two very different things. Loosely-coupled code is design discipline. Independently installable components is a deployment discipline. There is some degree of overlap though, because your design needs to support variable dependencies. For example, the application still needs to work if there is no sorting component found – it just wont sort stuff. As always, there needs to be some sort of core foundation, the essential code necessary to bootstrap the application. This is where the logic to determine what features are available live.

It might seem like overkill to divide your software into tiny installable components, one for every feature of the system. It might seem that way, even scary at times, what happens if the component installation fails for some reason? This isn't any different than installing the whole package as one big opaque box – buggy code is buggy code. Its better to think from the users perspective – maybe the majority of features we've implemented in the system are overkill to them. If they don't want them, they don't need them. So my advice is this – implement feature components as much as possible. Implement 400 of them if you need to, just don't ship them as one unit that cannot be decomposed.