Long Parameter List

Long parameter lists should be considered a Good Thing (TM). They are essentials in building powerful libraries and reusable frameworks. And we have a long list of empirical evidence that Long Parameter Lists are essential and unavoidable

Take any Unix command at random, even the simplest one, and run command --help or man command. For example: cp --help. On linux it shows two screens full of options:

Usage:
cp [OPTION]... SOURCE DEST

or:
cp [OPTION]... SOURCE... DIRECTORY

or:
cp [OPTION]... --target-directory=DIRECTORY SOURCE...

Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.

-a, --archive same as -dpR --backup[=CONTROL] make a backup of each existing destination file -b like --backup but does not accept an argument -d, --no-dereference never follow symbolic links -f, --force if an existing destination file cannot be opened, remove it and try again -i, --interactive prompt before overwrite -H follow command-line symbolic links -l, --link link files instead of copying -L, --dereference always follow symbolic links -p, --preserve preserve file attributes if possible --parents append source path to DIRECTORY -P same as `--parents' for now; soon to change to `--no-dereference' to conform to POSIX -r copy recursively, non-directories as files WARNING: use -R instead when you might copy special files like FIFOs or /dev/zero --remove-destination remove each existing destination file before attempting to open it (contrast with --force) --sparse=WHEN control creation of sparse files -R, --recursive copy directories recursively --strip-trailing-slashes remove any trailing slashes from each SOURCE argument -s, --symbolic-link make symbolic links instead of copying -S, --suffix=SUFFIX override the usual backup suffix --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY -u, --update copy only when the SOURCE file is newer than the destination file or when the destination file is missing -v, --verbose explain what is being done -x, --one-file-system stay on this file system --help display this help and exit --version output version information and exit

By default, sparse SOURCE files are detected by a crude heuristic and the corresponding DEST file is made sparse as well. That is the behavior selected by --sparse=auto. Specify --sparse=always to create a sparse DEST file whenever the SOURCE file contains a long enough sequence of zero bytes. Use --sparse=never to inhibit creation of sparse files.

Very powerful, flexible, and strictly necessary.

In OO frameworks, take for example the ACE C++ framework from Doug Schmidt. You'll see many constructors in there that have more than six parameters. Or take the Smart Pointer class from Modern Cee Plus Plus Design. It has four templatized arguments. Basically for a smart pointer which is one of the smallest interfaces you can have in C++.

That is so because the more abstract, the more usable a piece of code is, the more likely it is that it will need to be adapted to various local situations. In order to factor out commonalities, but still allow it to be used in exceptional cases, you create a Long Parameter List that allows the caller to adapt the abstraction to its local needs.

When people complain that this puts too much of a burden on the caller, it shows that people haven't programmed too much in advanced languages like Visual Basic, Modula Three, Perl or Unix shells. Basically that's a non-issue: you combine parameter passing by name and provide sensible default values for most of them, so that client code that doesn't need fine tuning doesn't need to suffer the penalty either. Or clients that need fine tuning in one aspect but not all the aspects can set only those parameters that are important for them.

For example:

cp source target cp --recursive source target cp --recursive --dereference --one-file-system source target

In a statically typed language the last one would look like:

copy ( sourcedir='source', targetdir='target', recursive=true, dereference=true, stayInOneFS=true);

Now I know that Long Parameter List hurts in Java, Cee Plus Plus (less than in Java) and the likes. But that is a language design deficiency, and can be solved (albeit with a bit more effort) by doing what I call Emulate Keyword And Default Parameters:

new CopyOperation(source,target) .recursive(true) .dereference(true) .stayInOneFs(true) .execute();

It almost looks as good as in Visual Basic, except it goes against the Grain Of The Language, but then that language doesn't have a lot of grain, anyways.


One of the Code Smells described in Refactoring Improving The Design Of Existing Code. A method that has too many parameters is asking to be refactored.

One reason is that a Long Parameter List increases the likelihood that two or more parameters will be of the same type/class, which may lead to coding of invalid calls.


This smell is quite unpleasant. -- Been There Done That

All you can read from the method's signature is "this is complicated".

It's infectious too. You're probably calling other methods from this one, and they need several of the parameters too.

It's worse when you have to add another parameter. You're going to pile it into the existing method and go round editing all calls to this, or you're going to copy the method under the new signature and make things even more complicated.

Sometimes you need to pass "null" or "undef" into some of the parameters, because they aren't relevant in the mode you intend the method call.

Fixing it will depend on how the parameters can be clustered together, but it's quite likely that you can separate them into two or three logical groups. Make a class that represents each group, or at least a struct.

Big Bang Refactoring to tear everything apart and put it back together the new way. You will Fear No Code if you have the Unit Tests.

Doing it more gradually is less fun.

Make yet another new method, which takes the new objects and calls the old methods.

You now have backwards compatibility, and therefore breathing space.

Go round deprecating or changing calls until they're all gone.

Long parameter lists look like this: msdn.microsoft.com

At least the parameter types in this sample have comprehensible and easily pronounced names like "LPCTSTR".

Opinions on LPLs differ, obviously, but sane people agree that they are Pure Evil(tm).

It's actually those who do not know what they're talking about.

Naming parameters in a call only solves one problem with LPLs, namely, that the function call would become unreadable. In combination with Default Values for parameters it also solves a second problem, namely, that the caller has to specify every parameter even if sensible defaults would do. Most fundamental problems with LPLs cannot be cured this way, though:

A function that just delegates a call has to accept and pass on all parameters explicitely:

void Master::doSomething(int a, int b, Foo foo, Bar bar, Moo moo, Meow meow) { slave.doSomething(a, b, foo, bar, moo, meow); }

This also means that a delegating function will break every time the signature is changed.

When Runtime Polymorphism or Compiletime Polymorphism is used, a method needs to know about and accept all parameters explicitely even if it uses only some.

It will also break every time the signature is changed.

While breakage can occur when shorter function signatures are used, the problem tends to be most rampant the longer signatures get. This is because longer signatures are often used when Signature Entropy is high. All of the above problems can be solved nicely using Parameter Objects. Their usage typically does not guarantee Binary Compatibility, but it does help a lot in maintaining Source Compatibility.

This piece of immature wise-cracking was brought to you by Arne Vogel.

I can see a lot of handwaving in the above. Incidentally the piece of code that you refer to has kept Binary Compatibility for over a decade now, so it is a rather spectacular example of how a well designed API for a rather complex domain can make use of Long Parameter List and provide an impressive array of power and functionality while keeping it stable for over a decade. Try to find a contemporary Linux distro out there that runs binaries from a decade ago, and good luck. Or how about if you find a device driver for Linux written in 2000 that runs on current kernel. Folks can bitch all they want about Windows, but if you look objectively it is an impressive engineering achievement.

Parameter Objects don't solve anything, they just move complexity some place else, while adding unnecessary entities to the system. You can group stuff that belongs together in parameter object, but not all parameters in a Long Parameter List can be sensibly crammed together in parameter objects. If you want to make your case you can show us how you'd design a better public interface for the cp command in Unix, or a better interface for the CreateWindow function under windows, and then we'll see. --Costin Cozianu

Hmmm... In Create Window,

int x, int y, int nWidth, int nHeight,

looks like a "Rectangle" to me.

Ok, so now I as a programmer I'd have to replace

CreateWindow ( ..., 100,100, 300, 200, ...)

with

struct Rect r; r.x= 100; r.y= 100; r.nWidth= 300; r.nHeight= 400; CreateWindow( ..., &r, ...);

I don't know if I'm so happy with the later.It's true that the Rectangle case can go either way. If you already had a rectangle in the calling context, you can pass it more easily. Rectangles can also be subject to geometrical transformations as one object, far easier than 4 separate variables. On the other hand, if you had 4 separate variables or constants like the above, or if you cared about performance, say you wanted to draw tons of rectangles in a game, than the unfolded variables may be arguably better. Some frameworks choose to provide all options in one overloaded function where the language supports that, while Microsoft SDK goes consistently with the unfolded four variables also in part because the language is C and because folks who need the folded version can easily work around the unfolded design, while the other way around does not work.

It's not clear cut, but even with the folded rectangle, Create Window would still have 8 parameters while Create Window Ex would have 9.

Microsoft APIs are rarely that consistent (the Win32 API was formed by accretion more than by design). [??? -- rather dubious claim]

For every function like Create Window that has a long hairy parameter list, I can think of another function that takes a single (hairy) struct. Sometimes this is because the call ends up going through the narrow Send Message pipe where you are only allowed a couple of parameters.

Also, if using Cee Plus Plus, you could choose to create the rectangle structure automatically using a convenience constructor if that is more concise:

CreateWindow( ..., CRect(100,100,300,400), ...);

So what would the inner CRect(100,100,300,400) accomplish ? I am affraid that not much.

[It tells the compiler, as well as future maintainers, what those 4 numbers sandwiched in there stand for.]

It tells the compiler but the compiler isn't happy to hear that. As for the proverbial maintainer, he either has to know the Create Window signature, or the Create Window and CRect signature. But anyways, that is small potatoes we are talking about. Even with the 4 sandwiched inside CRect, the function still has 8 parameters.

Replacing a Long Parameter List with an equally complex Parameter Object is something I would definitely recommend against, unless it is part of Emulate Keyword And Default Parameters, in which case we're still talking in essence of a Long Parameters List but with a workaround to language limitations. One needs to decouple the technical details related to Program Intoa Language, from what is really happening. Even with parameter objects, we're still talking about one logical operation (like creating a window) that depends on a long list of variables, because it needs to be adapted to a wide variety of contexts.

But with keyword and default parameters (either for real or emulated), I contend that Long Parameter List are often times needed to create powerful and reusable abstractions. --Costin Cozianu


Maybe this "window" thing, and GUI's in general, are better served with a markup language akin to HTML. Markup seems to do "mass attributes" better than most existing imperative languages. -t


See original on c2.com