I've been thinking this week about software plumbing (though infrastructure might be better word than plumbing). I'm referring to those parts of our systems that don't directly fulfill business requirements (though sometimes they do), but that we still can't do without.
When someone (particularly someone who is not a fellow programmer) asks us to describe a system, we seldom begin by describing the really cool logging system, or the elegant base classes and interfaces that we designed for collections, or the centralized exception handler. However, this infrastructure is as necessary for a system as the things we might normally talk about: business objects, user interfaces, reports, and database tables. No one talks about the system's plumbing during requirements meetings, but if the plumbing were missing it would stand out like a new house without a flush toilet.
Often, designing and developing the infrastructure takes more time than the it does for the "first class" parts of the system. Where this really gets us into trouble is in planning and estimation. We forget, when telling our project manager, "That should take about two weeks." (the default estimate for all tasks), that we don't yet have the infrastructure for logging, caching, or exception handling. Often it's not until we're in front of the keyboard that we realize (or remember) that we lack certain key infrastructure elements. This can happen, especially, after joining/forming a new team and when using new tools or languages.
The evil in this is not so much that we gave a faulty estimate to the project manager (which is bad enough), but rather that we're saddled with the temptation to hack our way out of the situation. When struck with the realization that we're missing critical plumbing, we have at least three options: revise the estimate (often not an option), hack in some shortcuts, or work extra hours to properly code the parts of the infrastructure we need. In practice, the solution is probably some combination of these.
Personally, hacking in shortcuts is so distasteful to me that when caught in this dilemma I'll work the extra hours or revise the estimate (the latter might especially be a good option if I was pressured to pull the estimate out of thin air without being allowed time for proper analysis--which is so often the case). In those situations where a shortcut is necessary (the quest for the ideal must always be balanced by the business realities), I at least want to take a little extra time to think about how I would design it given the time, then hide my shortcut behind an interface or other indirection layer to allow for easier refactoring later.
It is worthwhile, I think, to train ourselves to consider the state of our infrastructure (and our programming tool box) when estimating the duration and effort for a given task. It's definitely something I'm working on for myself, as estimating continues to be the single most challenging part of my job.
Good managers are aware of the estimation risks stemming from unknown requirements. However, as programmers we may have some work to do in educating managers on the infrastructure issues that can have significant effect on the quality of an estimate. This is especially true when combining the risk of unknown requirements with the risk of unknown infrastructure-related tasks, for often the former leads directly to the latter.
Some questions we can ask before giving an estimate:
- What is the state of my toolbox (common utility functions, database access, code templates, etc.)? If my toolbox is lacking, then every time I need a tool, I will need to stop and invent it, borrow it, buy it, or steal it.
- Have I done work for this client before?
- Have I done work on this project before?
- Have I done work on this part of the system before?
- Have I programmed in this language and/or paradigm before?
- Will the task require the use of any tools or technologies that are new to me and/or the organization?
- Do I already have a workable model of the system in my head?
- Do any of the requirements introduce a new "class" of problem that has never been solved before within the system? For example, if a new requirement says that the system needs to send an email, does the general capability to send emails already exist in the system? And, as a follow-on questions, has our team/organization solved this class of problem before--and is there anything re-usable there, even if it's just the design pattern?
- Are there bureaucratic obstacles that will slow me down?
- And of course, the all important question, how much do I know about the requirements at this point?
So what are these infrastructure elements that so often bite us this way? Here are some off the top of my head:
- Logging
- Exception handling and exception post-processing (logging, emailing, etc.)
- Test harnesses and unit testing frameworks
- Build scripts
- Database connectivity
- Configuration files and global data storage/retrieval
- Caching
- Object/Relational Mapping and Persistence Frameworks
- Session management (in a web context)
- Debugging infrastructure (particularly in distributed architectures)
- Distribution (install scripts, mechanisms for performing patches, etc.)
- Plumbing for searching, sorting, paging, and presentation of large data sets (common in a web context)
- Encryption
- Load and stress testing
- Code templates and code generators
- Transaction design strategy
- Faxing, emailing, etc.
- Report/document generation and distribution
- Installation programs
- Bases classes, interfaces, namespaces, etc.
If we're lucky, our out-of-the-box toolset (for example, libraries, runtimes, frameworks, IDEs, operating systems, etc.) will provide some of the tools we need. Even in those cases, however, we need to be wary of excessive optimism, for at least a few reasons:
- Just because I have an existing tool does not mean I've used it before; there will likely be a learning curve.
- The out-of-the-box tool may have limitations I did not consider, making the tool unusable for my particular purpose.
- The out-of-the-box tool may not work exactly the way I expect, requiring me to augment it or write glue code to supplement it with some other tool.
The problem of unexpected limitations bit me when I started my first Microsoft .NET DLL project. I started the task assuming that my need for data-driven settings would be served by the CLR's built-in "AppSettings" capability, which I had read about and used in other .NET projects. However, I soon found out that those *.config files are only supported for executable assemblies (not DLL libraries). So I was left to roll my own solution, which of course took time.
My original version of this essay failed to include any mention of configuration management-related issues. It's easy to forget this because it is not "writing code." It's also easy to forget because often there is such good tool support. Nonetheless, configuration management definitely adds overhead to the coding process and must be taken into account. Some items to consider (not all apply to all situations):
- Creating new build scripts
- Updating existing build- and configuration-management related scripts and configuration files when adding, changing, or removing components in an existing system
- Figuring out and controlling who can do builds, on which machines (a big factor in environments that use a lot of shared libraries and dynamic linking)
- Deciding how and whether builds are automated
- Integrating builds with automated tests
- Checking in and labeling builds in the source code control system
I think there are also other kinds of tasks that we forget when estimating:
- Configuring servers and middleware
- Preparing release notes, installation instructions, and other must-have documentation
- Preparing test environments
- Creating test data and/or cleaning up old test data
- Code reviews
- Unit testing
Can you think of additions to these lists? Please post your suggestions using the Comments form.
Dan