One thing I’ve noticed about my code is that an awful lot of the
comments are of the form
call_some_function(); // XXX - Error-checking
The proximate reason for this accumulation of “do something smart if
something goes wrong to-do items is that a lot of the time, the
function in which this appears doesn’t have a mechanism for reporting
errors, so I don’t know what to do if I detected a run-time error.
This leads to the other big problem, the one where I’m calling another
function of mine, which doesn’t report errors, so I can’t even tell if
something went wrong.
b() which calls
c(), all three are likely to have
XXX - Error-checking
c() doesn’t know what to do in case of en
a() don’t even know how
to detect errors. And so the
XXX comments accumulate.
For me, this is often caused by the fact that experimental programming
and production programming are quite different: when I’m learning a
new system, such as a new graphics or math library, I want to figure
out which functions I’m supposed to call to get the results I want,
what the various data structures do, which one of multiple approaches
to the problem works best, and so forth.
If I set up a test environment (e.g., a database server so I can play
around with database-manipulation code), I’ll be keeping an eye on it
to make sure that everything is sane, so my experimental code needn’t
worry about checking whether the server is up. And if it can’t make a
connection, it’ll likely dump core; but since I don’t have any
precious data or users who’ll yell at me if things go wrong, it
doesn’t matter. It’s best to just set up a working environment and
hammer at the code until it works. I tend to accumulate a large number
of ad hoc modules, functions, data files with names like
foo2, and so forth.
In production, of course, this isn’t good enough: all sorts of things
can go wrong: servers go down, network connections get broken, runaway
processes suck up all available memory, users try to open nonexistent
files, viruses try to overflow buffers, and so forth. Code needs not
only to detect errors, but deal with them as gracefully as possible.
But if I’m working on a larger project, and working on adding new
functionality that I’m not familar with, the temptation is strong to
take the “learning” code that’s been hammered into some kind of shape,
and plop it in the middle of existing production code.
Neither approach is inherently wrong. Each is appropriate in certain
contexts. You want to keep things loose and fluid and unstructured
while learning, because by definition you don’t know what’s going and
what’s best. And you want to have things organized, structured, and
regimented in production, to make it easier to avoid and find bugs, to
ensure code quality and stability.
But this difference does mean that it can be hard to integrate new
code into production code.
Often, test code is so messy that there’s really no choice but to do a
complete rewrite. Neater test code is worse in this regard, since it
may trick you into thinking that with just a little bit of cleaning
up, you can use it in production.
So it’s important, when moving from test to production code, to ask
oneself how errors should be reported. This takes a bit more planning
ahead of time, but like security, it’s easier to build it in from the
start than to retrofit it onto existing code.
The thing that works for me is:
- Think of a function to add.
- Write a comment describing the function: what it does, which
arguments it takes, what value(s) it returns, and what it does in case
- Write the function.
In that order.
This may be more structured than you want in the playing-around phase,
in which case you should definitely consider using it during the
hammering-into-shape phase, after you have the basic functionality
working, and before you’ve started moving test code into production.
Over time, though, this kind of forethought may become automatic
enough that it doesn’t get much in the way of experimentation.