Understanding Degrees of Code Flexibility
In some projects I’ve been managing of late, I’ve noticed a continuous question cropping up: How flexible should we make the different parts of the system? I’m currently working with a bright crew of people, so they’re picking up on this quickly, but I thought I’d do a bit of a write-up to help the process along. And, as long as I’m doing write-ups like this, I might as well post them.
In discussions of software, there is a large issue that gets lost in the shuffle. You frequently hear people argue the merits of different styles of or approaches to programming. Unit testing or not? TDD? IoC or inline new? What’s the appropriate size of a method? ORM or inline SQL? SQL at all or NoSQL? You get the idea. But one thing that I find is often glossed over is the idea of system changes as a function of ease of making those changes. In other words, if a user comes a-hollering and says, “I want, nay, demand the ability to do X,” how hard is it to make that happen and to verify the results?
And by “hard,” I don’t mean, “do you write code for a day or for three weeks?” I mean, “what do the changes look like in terms of risk and deliverables?” In other words, can you make that happen by changing a configuration file or does it require code changes? Will you need to re-deploy or can you somehow patch on the fly through a plugin architecture? And is it testable? Can you verify 99% of the changes by swapping out a configuration setting, or do you have radically different production and test setups?
So I’m going to define some concepts to flesh out an idea. This isn’t exactly a formalized theory or anything. It’s rather just a working lexicon of how I think about my application. This is a scale of system flexibility for a given future change. Or, put another way, here is a way of assessing how much effort on the part of the entire development/operations group doing X for the aforementioned user will be, from least to most significant.
- Users can do it themselves.
- An IT-level change is required (e.g. changing a config file, swapping out images, etc.)
- An architect/dev change is required to configuration (e.g. XML for an IoC container)
- A non-compiled source code change is required (e.g. you update the markup for a site but not the underlying code)
- An Open/Closed Principle Compliant source code change is required (basically adding new code).
- A localized tweak to existing code is required.
- A substantial change to existing code is required spanning various modules.
Now when considering this list, it makes sense to assess your change-set in terms of the furthest down thing it requires. So maybe you need to change a logo on your website, which is easy, but you have an unwieldy switch statement somewhere that chooses swaps it out in certain circumstances that you now need to change. This is going to be probably a 6 rather than a 2. A given change is going to be as rigid as the most rigid link in the chain, so to speak.
Here are the kinds of changes described in more detail.
Users do it themselves.
There are some sorts of changes to the system that need not involve anyone from your team/staff/company. These are things that users do, through the application. An obvious example is a banking or commerce website in which users can change their passwords. “Password” has nothing to do with the business logic of commerce, so this is functionally a meta-piece of administrativa that you’re entrusting to users.
A dev-ops or operations person changes meta-data in production.
This is something that you (hopefully) don’t trust a user to do but that doesn’t require any actual knowledge of the code base. A good example of this might be a desktop application that has an XML configuration file (or an INI file, if you’re willing to show your age with me). This file might be modified to have the application point to a different database or log to a different file or something. This is not something the average user could or should do, but it’s a relatively lightweight change in that it requires only minimal training and no re-deployment of any kind.
An architect or developer changes meta-data in production.
The next step down in operational rigidity is a meta-data change that cannot be performed without an understanding of the code base. The best example here is the configuration of an IoC Container that has been extracted to XML. Figuring out which service is used by which ViewModel is not something that anyone without sophisticated knowledge of your source code can do, but, on the plus side, it’s still just a change to a setting in production.
Someone changes source code that does not involve re-compilation
This is what happens when a developer logs on to the web server and stars editing HTML or CSS or even a server side script like PHP in the files. This is really not a good idea for a variety of reasons, but it is possible and may be something you have to do in a pinch, so it’s worth noting.
Someone makes a code change that more or less just involves adding code and very little modification
Now we’re down deep enough into rigid territory that a new deployment/install is required in order to push the changes. From here on, this cannot be done in production, so if you’re doing this you’re going to incur all of the overhead of building/running automated tests (hopefully), quality assurance, creating a deployment and deploying (your process may vary). But on the plus side, this is pretty low risk as far as code changes go. Adding things is generally both easy to verify for correctness of functionality and unlikely to mess up existing code.
Someone makes a code change that involves lightweight changes to existing code.
The most common scenario here is probably a bug fix, though it may be new functionality too, depending on how flexible your architecture is. This is a higher risk proposition than adding new source code because you’re now creating a risk of regressions. You still have all of the same considerations about build and deployment, but the risk of problems is higher. Your testing and verification overhead should also be higher. This is a heavier change.
Significant work on the code base is required.
This is what happens when a code base that models a company and implements Office as a singleton suddenly needs to accommodate the new office you opened up in Texas. You designed your code under the assumption that there could only ever be one office location, and you were right about that — right up until you weren’t. Oops. Now things get ugly because management comes to you and says, “we’re opening a new office, so the application is going to need to handle that” and your response is, “that’s completely out of the question. Why, even the thought is preposterous!” You tell them that substantial rework is going to be required.
Making Sense of your Options
So why did I list all of these out? Well, I did it because I feel it’s important to know what your options are when you’re designing and that it’s important to anticipate, rather than react knee-jerk style, to changes. If you sit down before you start putting together a code base, think about what users might want and then go through the exercise of figuring out which number of change would be required. If you find likely changes that would be 6s or 7s (and there is certainly a sliding scale at the 6-7 level), that’s a problem that you should start addressing now. If you find extremely unlikely changes that are 1s and 2s, that’s not necessarily a problem. But it is a point of design flexibility that you could get away from, and it may be that you have pointless abstractions and complexity (though I’d be a lot more hesitant to introduce rigidity because you think flexibility is unneeded than vice-versa).
Another interesting exercise is to consider categories of these things. For instance, 5-7 are all things that require compiled code changes and 1-4 are all things that do not. This is an interesting way to split up your functionality, and it’s obviously the backbone of this post, but you can divvy these up in other ways as well. For instance, if you’re writing software that for some reason has no field or ops support, then 1, 5, 6 and 7 are your options, and 2-4 are basically non-starters. Or, if you’re considering things in which source control is an issue, then 4-7 are in a category and 1-3 are in a different category (most likely, as I’d think that you’d favor generating meta-data files as part of your deployment rather than source controlling different configurations).
None of this is even remotely comprehensive, but my goal here is really just to encourage people to understand at design time the difficulty of changing something at production time. It seems quite often to be the case that people don’t really think about this, and simply because no one has ever pointed it out to them. Your mileage may vary on the number of categories in the list and your preference for certain options, but at the core of this is a basic and incredibly important idea: you should always play “what if” when it comes to changes that users might request and understand how much of a headache it will be for you if the “what if” comes true. Oh, and also try to minimize the number of headaches. But hopefully that goes without saying.