A danger of default values for function arguments (in illustrated form)

October 18, 2012

Due to recent events, I've been working on a program to measure our disk IO latencies. Since it only needs timing accuracy in the millisecond range, I've been writing it in Python (which is more than fast enough to not add distortions to the IO timings). In the process of developing this code, I made a classic absent-minded mistake that shows a danger of default arguments.

The code needs to know the size of the range it will be doing IO on. In the beginning, it worked only on files and got the size from the size of the file, and the code looked something like:

def process(fname):
  size = sizeof(fname)
  [.....]

def main(args):
  for a in args:
    process(a)

Then I discovered that I needed to make the code work on raw disks too. Getting the size of a raw disk is much more complicated than getting the size of a file and anyways, they're huge and I didn't want to do that much testing. I decided that clearly the thing to do was give process() an optional argument to specify the size to work on and revised the code to look like this:

def process(fname, size = None):
  if size is None:
    size = sizeof(fname)
  [....]

def main(args):
  size = None
  [set 'size' from a -s switch]
  for a in args:
    process(a)

I then spent an embarrassing amount of time trying to figure out what was wrong with my code such that the IO offsets weren't being computed right (there were complicating factors, evidently including insufficient coffee).

The problem (as you could probably see immediately) is that I'd forgotten to update the code in main() to actually pass the new size argument to process(); I'd only added the code to optionally get it from the command line. If I had not cleverly made size an argument with a default, I would have discovered this mistake immediately because Python would have reported an argument count mismatch. But using default argument values hides argument count errors so when I left out an argument that was in practice not optional I didn't get an error, just an oddly broken program.

(I was lucky that things broke in a clear, directly observable way.)

This is not the first time I have revised function arguments this way. When I add a new argument to a function I always have a temptation to give it a default and make it optional; there's a little voice in the back of my head that says 'this is the right way to keep existing code working'. Very often this is clearly wrong for the same reason as it was here, namely that I'm going to immediately revise all of the callers to explicitly pass in this new 'optional' argument. If I'm always passing an argument, giving it a default value too is simply inviting argument count errors. I really should know better by now.

(There are cases where default values for arguments are really useful, but this is not one of them.)

Written on 18 October 2012.
« Operators and system programmers: a bit of System Administrator history
An old Unix trick for saving databases »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Oct 18 01:33:29 2012
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.