11-21-03 SPO: Solve the Problem Once
Dave Thomas and Andy Hunt, authors of The
Pragmatic Programmer, have a principle they call DRY: Don't Repeat
Yourself. The phrase suggests repetition in code, such as duplicate code that
could be refactored into a single function. This would make it a different way
of saying "Once and Only Once." However, based on comments they've made in
interviews, I suspect that (despite the cleverness of "dry" as an acronym) they
would rather have called it "SPO: Solve the Problem Once."
In this interview, Dave
said that "The idea behind DRY is far grander than that [you shouldn't duplicate
code]... DRY says that every piece of system knowledge should have one
authoritative, unambiguous representation." (This sounds more like a description of the SPOT rule:
Single Point Of Truth.) To me, this says "when you solve a
problem, solve it so that you don't have to solve it again, later."
Since finishing
Thinking in C++, Volume 2 (in print this December),
I've been doing a little retrospective to discover what could be improved in the
process, especially since I was working with a coauthor, Chuck Allison. Over
time, I've discovered tools to be one of the foundations of the book development
process, or any process, so that's one of the things I've been trying to
improve.
One of the tools that remains part of my core toolset is make, despite
the existence of all sorts of open-source attempts to "make a better make." I
applaud these attempts and hope that I can switch to something better someday,
but in the meantime, make is what works. You can test and invoke just
about anything with make.
One of the issues with make is configuring it to know where your
various programs and directories live. This is generally accomplished with
macros:
TOOLLOCATION:=C:/MyTools/Buzzer.exe
DIRLOCATION:=D:/TestDirectory/code
As long as these are on my own machine, I can just set them in the makefile and
not worry about it. Of course, when I move the project from my desktop to my
laptop, the paths might be different, and when the project directory gets copied
to Chuck's machine, everything is different.
My first solution was to "separate the things that change from the things
that stay the same" by extracting the directories that seem to change from
machine to machine, and put them in their own .defs file, which the makefile
would then include. There was a desktop.defs, laptop.defs and Chuck.defs file.
This sort of worked, but it seemed like maintaining all three files in the same
directory didn't quite work. When I sent Chuck a new version of the directory,
his old settings would get overwritten with whatever I had. I do find it
very helpful to keep everything about a project in a single directory
that way, "installing" it on another machine is simply a matter of copying the
directory. The simplicity of that approach is quite valuable, but if important
information is getting erased in the process then it isn't working.
Platform-specific information needs to be separated from project-specific
information, and the project always needs to know where to go to find the
platform-specific information you need a repository. The single point of
truth is a recurring theme in program design, and is the proper
justification for the Singleton design pattern (as opposed to using it as an
excuse for global variables).
My first thought was to use the Windows registry. Python has tools to read
and write this, and while the book code works on other platforms, the tools to
build the code tree are designed for Cygwin
under Windows. Fortunately, this impulse passed why should I take the
responsibility for messing up someone's registry and possibly whacking their
machine? And for what? ("Let's try something complicated" always seems to be my
first impulse, so I try to sit down and breathe until I remember "Do the
simplest thing that could possibly work.") However, the concept of the
registry as a single point of information is still valid, so why not just make
my own variation of the registry a plain text file whose name and location
is specified by a single environment variable,
containing configuration information, and the tools to read it. (under a *nix environment,
you can just put your configuration file in your home directory; note that I call the
python function os.path.expanduser in case you use a ~ in your path).
I did this, and then discovered that Python already has a module called
ConfigParser that automatically parses Windows-style configuration files. This
even includes parameter substitution.
The final tool consists of a general-purpose config file reader called
ConfiguratorParser
(this can be used within any Python program to produce configuration information), and
a very simple tool, Configurator,
that uses ConfiguratorParser and creates a def file which can then be
included into your makefile. You invoke the program inside your makefile, and
include the resulting .def file, with the following makefile lines, assuming
PROJECT has already been defined as the name of your project:
$(PROJECT).def: $(CONFIGFILE)
Configurator.py $(PROJECT)
include $(PROJECT).def
The problem that this solved was, in the grand scheme of things, a relatively
small annoyance. But my experience is that the little annoyances build up to
throw you off track enough during critical junctures such as when you are
in the heat of problem solving, trying to figure something out, and then you try
installing a new version of the whole project and have to stop and reset your
configurations that it's worth the day that it took (in this case) to
research and solve the problem once so you don't have to solve it again and
again. Solving the little problems once may be the key to achieving productivity.