Wednesday, March 21, 2012

A Checklist for Designing Software

Sometimes when we are developing software, developers can suffer from "forest and trees" syndrome (being unable to see the forest due to all the trees in the way). We get caught up in the details, or time pressure comes to bear, etc. We then fail to remember the overarching requirements for any software system. It's not like this hasn't been written about many times, but I've distilled it down into a short list of the most common requirements in order of their priority. Sometimes, a particular application will add requirements, cause requirements to be re-ordered, or have particularly heavy weight associated with one or more of the requirements. But I find this a useful touchpoint when building, modifying, enhancing, or rebuilding just about any software. It's not exhaustive, but it helps me keep things in focus.

In typical priority order:

Does it work?
Our software needs to produce correct outputs or at least flag when it suspects it is failing to produce correct outputs (it's amazing to me how often developers put so much thought into the "happy path" and almost none into detecting and handling errors).
Is it secure enough?
Sometimes this is a noop. Sometimes it's one of the most crucial considerations (a cryptographic algorithm being the poster child here). And security is much harder to add after the design is mostly complete. It's best to get on top of this one as early as possible, as security requirements that clash with a system design can cause problems in every other category.

Does it meet our scaling needs?
If our software does what we want but can't handle the required concurrent requests or total volume of requests quickly enough, it is effectively non-functional. Often back-of-the-napkin estimates are good enough to understand the basic problem here. Are you dealing with tens, thousands, millions, or billions?

Is it as simple as possible?
The smaller and simpler the code is, the easier it is to wrap your head around it and the easier it is to troubleshoot. Also, the less you might have to rewrite to meet a new or altered requirement,. This isn't an excuse for skipping error checking/handling, logging, security etc. Rather, we should implement those things as simply as possible. Sometimes, this may mean using a framework that helps solve part of the problem (e.g. an dependency injection container). Sometimes, it means not using a framework because that framework is overkill for the problem, overly difficult to use, or too opaque.