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:

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:

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.)

Comments on this page:

From at 2012-10-18 03:53:20:

It's not related to default parameters. It's an issue related to changing function semantics without changing its syntax. The same would apply if you haven't added the optional parameter.

By cks at 2012-10-31 18:36:54:

My (delayed) reaction is that 'changing function semantics' is an awfully abstract description of the problem I ran into, one that covers a large amount of ground. If we want to talk abstractions, I'd say that the real problem is not that I changed function semantics but that I half-changed them, leaving the old semantics intact as well as introducing new ones.

(I was lucky in my debugging in that I was applying the program and thus the old semantics to an area where they didn't work at all, as opposed to an area where they worked but gave subtly wrong results.)

From at 2012-11-04 07:53:01:

[...] 'changing function semantics' is an awfully abstract description of the problem [...]

I agree completely. I don't like this description either, because it doesn't provide any sensible way of avoiding the problem in the future. I just couldn't agree to boil the problem down to simple "default params are dangerous" statement.

By cks at 2012-11-08 13:38:26:

I think I've been unclear in the original entry. I don't think that default parameters are dangerous by themselves. What's dangerous (in my example) is using default values for parameters that aren't actually optional in practice, in situations where I should be supplying a value all the time (and they're dangerous for the reason that I highlighted; they hide argument count errors like not supplying that 'in practice not optional' new parameter).

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, View Normal, Add Comment.
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.