Summary
How much method overloading is a good thing? How much is just bewildering?
Advertisement
Ain't it nifty when you can call a method to draw an image with nothing more than a Graphics object and an Image object? Wouldn't it be nice to be able to tell it where to draw, too? What about being able to specify whether to stretch or crop it? Yes, yes, these things are all nice and there are many more. At what point do all these different choices become more of a burden than a convenience?
Obviously, the possible combinations of overloaded methods explodes as the number of options increases. It is not hard to understand why API programmers can seem to get carried away and offer too many overload choices. Overloading methods provide a clean and simple way to add flexibility to an API, but at some point the simplicity diminishes and the value of overloading rapidly decreases. Where is this point? It probably depends on the particular reader, but also on basic cognitive capabilities of humans in general (the number seven comes to mind as being the place where most eyes begin to glaze over).
I recently needed to use the DrawImage() method of the Graphics object in Microsoft .NET. I was horrified to find that it had no less than 30 overloads! *
I had been coding happily along when I came up against this veritable Medusa of methods. I foolishly stared straight into the face of this beast and became completely derailed. I was dismayed that I had to stop my train of thought and sift through this massive list of overloaded methods to find the one that was right for me.
(It was almost as annoying as not being able to come up with decent metaphors to mix while writing about it.)
Even with this ridiculous number of overloads, I had no option but to choose one that didn't quite match my needs. The one I needed had the parameters I required and several that I didn't (I wanted to resize the destination image, but I didn't care about using the abort callback, or selecting a sub-region in the original image). In other words, there weren't enough overloads! No, I'm not asking for more (please, please, no!), I'm asking for something different.
How Overloading Can Be Reduced?
How do you solve this problem? It connot be intractable. Can it? In any event, the first step toward solving it is at least caring about it.
Some languages don't have this problem. For example, in Python you can't have a bunch of different overloads of the same method. Instead you can use named arguments (it really boils down to using a dictionary). You would have a single drawImage() method whose usages look something like this:
graphics.drawImage( image, x = 100, y = 100, border = True )
In this case, the parameter for the image object is not named, but all the rest are. This idiom is commonly used in Python libraries, such as graphical libraries, where methods could otherwise have many parameters.
In Java, you could simulate this with a Map, but there would probably be great hue and cry if you suggested this, since it would contain a heterogeneous collection of objects. Its usage and implementation would probably be too clunky anyhow. For example, usage might look like this:
Map params = new HashMap();
params.put( "x", new Integer(100) );
params.put( "y", new Integer(100) );
params.put( "width", new Integer(100) );
params.put( "height", new Integer(100) );
params.put( "border", new Boolean(true) );
graphics.drawImage( image, params );
The implementation of the drawImage() method would be even clunkier: You'd have to pick out all the input objects from the Map, by name, then cast them to their appropriate types, unbox some of them, deal with wrong types, etc. and then you'd probably just end up dispatching to private overloads anyhow.
C# could handle the variable parameter list much better than Java, using the params keyword. The method would be implemented like this:
public class Graphics
{
// ...
public void drawImage( Image image, params object [] options )
{
// Unpack all the possible parameters.
}
// ...
}
However, in Java and in C#, this is circumventing the whole statically-typed convention that is central to these languages.
Another solution is to create some helper objects to aggregate parameters. This is common in C, where you create a structure, zero it out and populate the relevant elements. Of course, C doesn't have the option of overloaded functions, so other than having zillion parameters, that is really the only option there. I haven't seen this done much in object-oriented languages.
C and C# also have the ability to handle variable-length argument lists. In C, it is not very useful unless the first parameter in the list tells you what type the other parameters are (or unless you come up with some scheme to make them self-descriptive). In C#, you get an object array, so you could use reflection on each of the elements, but that would hardly be elegant and wouldn't let you distinguish between objects of the same type (is an int the x coordinate? y? width? height?).
Another idea would be to have the target object collect information, like this:
This is kind of like the manipulator usage on C++ I/O streams and it is pretty clunky. The alternative of setting all the drawing attributes on the image object instead is nearly equally clunky, since these are really attributes of the drawImage() action, not so much of either of the objects.
Is there an elegant solution for this problem in Java and C#, or is this just something we have to live with in statically-typed languages?
* To add to the clutter, many of these overloads had very long parameter lists, several having up to 10 parameters. Too many parameters is another egregious practice in my book, but I'll save that for another rant.
Is there an elegant solution for this problem in Java and C#, or is this just something we have to live with in statically-typed languages?
No & No (ok, I've not thought about it & No)
Seems like some of those overloaded methods could go away in Nice (a statically typed OO language that compiles to Java bytecodes).
Nice methods may have optional (default) parameters.
If it was ok to have the int params automatically converted to floats within the method scope then maybe some more could go away courtesy of int float subtyping.
Another possibility is to use one or more intermediate objects to group the plethora of arguments:
Style style = new Style();
style.setBorder(true);
graphics.drawImage(image,
new Rectangle(100, 100, 100, 100),
style);
I decided on a Rectangle argument to handle the geometry, and a second argument (of hypothetical class Style) to capture other aspects of drawing an image. Note that this factors less common options into a different object.
Some people will whine about "unnecessary" allocation, but a) the objects can be reused, and b) everyone keeps saying the JVM can GC small objects easily. (Also, doesn't C# have a "struct" or "value object" type that's more efficient than ordinary classes?)
To paraphrase Bertrand Meyer, any method that has more than three arguments almost certainly indicates one or more missing objects. James Noble wrote a paper called "Arguments and Results", available from http://citeseer.nj.nec.com/107777.html , that gives a few patterns for handling complex object protocols; the above pattern is called Arguments Object.
> Is there an elegant solution for this problem in Java and > C#, or is this just something we have to live with in > statically-typed languages?
The elegant solution (in fact, I think, also the cause) for this problem (In fact, I think it's more of a phenominon) is the modern IDE, especially with the feature called intellisense.
Maybe I have not seen or heard of, but there seems no IDE for dynamic (or loose) typing language that support the feature on par with static type language IDE's like VS.NET and IntelliJ, or it's just impossible (e.g. the AUTOLOAD for perl). Will be very glad to know that such IDE exists, and try it.
I don't think it's a problem to worry about, maybe a problem when the javadoc or ndoc is shown to the users, but with good tools, it shouldn't be a big problem.
And if a few overloaded methods out of many is often used (80/20 rule?), I would write one method that takes a wrapped up object containing the parameters (something like the Map thing) and then delegate the call to the appropriate overloaded method. It still provide the type-safty benefits. Performance issue? well, not really, but if insist, it can be done easily with refactoring feature provided by, again, the IDE.
> Another possibility is to use one or more intermediate > objects to group the plethora of arguments: > >
> Style style = new Style();
> style.setBorder(true);
>
> graphics.drawImage(image,
> new Rectangle(100, 100, 100, 100),
> style);
>
I think this is the best solution for a language with strong static type checking. It has almost exactly the same semantics as the Python keyword example: in both cases there is an object with a complete set of default values that can be selectively adjusted by the message sender. The difference is that in Python the default object is created inside the receiving method, in Java/C++ the message sender creates the default object.
It also has the considerable advantage that new arguments can be added over time without changing the interface and still gracefully handling older clients.
> Ain't it nifty when you can call a method to draw an image > with nothing more than a Graphics object and an Image > object? Wouldn't it be nice to be able to tell it > <i>where</i> to draw, too? What about being able to > specify whether to stretch or crop it? Yes, yes, these > things are all nice and there are many more. At what > point do all these different choices become more of a > burden than a convenience?
OK, let's provoke a real discussion here. I say the point at which the choice between N overloaded methods is a burden is N > 1
I am almost entirely serious. Methods should have a clear and obvious usage. Lots of overloadings in the name of "convenience" too often means "I haven't thought about this properly." I find that restricting myself to a single definition (with default parameters and/or complex argument objects) is a really good way to discipline my thinking.
It also follows the software engineering principle of doing things in one place only. I'm always wary when I have to update or override an overloaded method because of the potential traps. Do all the variants in the original end up calling a single implementation with the full set of arguments, or do they duplicate code? How many variants will I have to inspect before I can be really sure that I've fixed a bug?
For the client it can be equally confusing, as in the game of determining "which method am I actually invoking?" It's not fun finding that the subclass designer forgot to provide a new definition for one overloading of a method, so one set of argument types in the client code invokes a superclass method while another set of arguments types works as expected.
We go to so much trouble to keep names as unique identifiers in other areas of computing, why not in class design?
Another solution is to create some helper objects to aggregate parameters. This is common in C, where you create a structure, zero it out and populate the relevant elements. Of course, C doesn't have the option of overloaded functions, so other than having zillion parameters, that is really the only option there. I haven't seen this done much in object-oriented languages.
As others have said, this is pretty much the way to do it. Interstingly, it's the one you skipped over.
Strangely it hasn't caught on. The java.util.regex classes still use the ORing of ints to set options, when a class would do a much better job.
> Another possibility is to use one or more intermediate > objects to group the plethora of arguments: > >
> Style style = new Style();
> style.setBorder(true);
>
> graphics.drawImage(image,
> new Rectangle(100, 100, 100, 100),
> style);
>
>
[snip]
I agree with Frank here. Whenever you see multiple choices you have hidden objects. Most libraries are bloated with bad design and programming, and we suffer from this bloat. If the anonymous class syntax was nicer in Java we could redefine fields in the anonymous class declaration, to minimize overloading:
graphics.drawImage( image, new ImageOptions() {x = 100; y = 100; border = true;} );
graphics.drawImage( image, new ImageOptions() {width = 400; height = 200;} );
graphics.drawImage( image, null );
A pet peeve of mine is looking at another developer's code/API and branding it "bad programming". You don't know the context under which is was developed. All development projects are done under less than ideal constraints. Less than optimum designs make there way in. This is not necessarily bad programming. It's fine to point out deficiencies - but leave the moralizing out, please.
> A pet peeve of mine is looking at another developer's > code/API and branding it "bad programming". You don't know > the context under which is was developed. All development > projects are done under less than ideal constraints. Less > than optimum designs make there way in. This is not > necessarily bad programming. It's fine to point out > deficiencies - but leave the moralizing out, please.
I'll start with a disclaimer: It's very hard to develop a good API. Libraries walk the thin line between too few functionality and bloat. I know this. It took me one year of research to come up with the interfaces and contracts of a collections API, and only because I had many available libraries to study (e.g. java.util, STL, Smalltalk, Eiffel). Also I'm a professional developer, working against deadlines and feature creep, so I know about "less than optimum" designs (some created by me, some just maintained).
Saying that, I have a strong feeling against badly designed libraries. If your application has design flaws (i.e. it's correct but maintainance is a PITA) only you and your team will suffer. But if one library has design flaws each and every of it's users will have to pay for it one way or another. Sure there are things outside the library writer control (e.g. language syntax and semantics), but most of them are under his control.
Of course in order to assess this "goodness" metric we must define our variables. I never formalized this before, and only will if required, but my general guideline matches these:
I'm not trying to "moralize" anything (but we all know that long parameter lists are the work of the devil ;), just giving my opinion. My "bad programming" statement has an implicit set of criteria that it uses to judge bad from good, but it's a metric, a fact. The environment constraining the development is an explanation, not an excuse. If you came to me and said: "you library sucks", I would answer (as I've done in the past) "give me a metric/test proving it and I'll fix". No one is perfect, but everyone is responsible for what he does.
P.S. if you're interested, my criteria to judge OO code is: CCN <= 5, method lines <= 15, method parameters <= 3, class methods <= 15, no cyclic dependencies, all tests green. Judging design is more difficult, as we shouldn't look the implementation, but it's probably in these lines.
> I say the point at which the choice between N overloaded > methods is a burden is N > 1
What about methods like abs():
int abs(int n); float abs(float n); double abs(double n);
without overloading... (just like old C)
int absi(int n); float absf(float n); double absd(double n);
... or the interpreter/compiler would magically identify the return type?
Method overloading is nothing but a tool.
If used correctly, it can provide nice abstractions, keeping methods with same semantics with the same name (like multiplications of integers or matrices).
If abused, it may be a programmer's nightmare, just like any other programming/language tool (inheritance, dynamic types, transparent remoting, operator overloading, or even Ant, XDoclet, EJB, XML, IDEs, etc).
When to use *the right* tool is a challenge of every software programmer/architect. Maybe we just need more experience (carpenters don't cut wood with hammers).
> What about methods like abs(): > > int abs(int n); > float abs(float n); > double abs(double n);
Well, in an O-O language that's not a very O-O way to do things. I'd expect to see int i = n.abs(); float p = q.abs();
Most O-O languages (Java, C++, Smalltalk, Python) in effect allow overloading on the first argument.
Or you could have just double abs(double n);
which will also work just fine for floats and ints through type conversion (and unless you have very strange floating point, no loss of precision).
> Method overloading is nothing but a tool. > > If used correctly, it can provide nice abstractions, > keeping methods with same semantics with the same name > (like multiplications of integers or matrices).
It may be a tool, but tools encourage certain ways of thinking. In a non-overloaded O-O language, or by imposing a "no overloading" rule upon the design in Java or C++, it's very easy:
The designer has to think, hard, about the method use. Once the choice is made, that's it. The maintainers don't have to worry about different versions. The clients don't have to decide which version to use. Even the documentation becomes shorter and easier to search. A whole area of "cognitive overload" just vanishes and everyone can think about something more important.
OK, maybe I'm just dim. But I like things to be as simple as possible.
> When to use *the right* tool is a challenge of every > software programmer/architect. Maybe we just need more > experience (carpenters don't cut wood with hammers).
Too many of the overloaded class designs around remind me of the home handyman types who have ten different saws and thirty types of screwdriver. Professionals seem to get by with less, not more.
Invoke "When I were a lad...": the most influential O-O GUI system was probably the Xerox Parc Smalltalk system. The most widely used would be Apple MacApp, Microsoft MFC, the X Windows Toolkit, and today Apple Cocoa. All of them succeeded without overloading (and only single inheritance too) so based on the experience of those programmer/architects it's tough for me to see overloading as anything but a complication.
>Well, in an O-O language that's not a very O-O way to do >things. I'd expect to see > >int i = n.abs(); >float p = q.abs();
To me, it just sounds like academic puritanism. How would be your expressions? Instead of
d = Math.sqrt(x*x+y*y);
should we use
d.setValue(x.multiply(x).add(y.multiply(y)); ?
>Or you could have just >double abs(double n);
Yeah, then even if we are using only integers we would have to cast doubles to ints every time?
c = (int)(Math.abs(a)+Math.abs(b)); ?
>It may be a tool, but tools encourage certain ways of >thinking. In a non-overloaded O-O language, or by imposing >a "no overloading" rule upon the design in Java or C++, >it's very easy:
And garbage collection encourages new programmers not to learn how to better manage memory. I'm not saying none will try to learn it, just that any tool can be misused. Only who has a driver's license is allowed to drive cars. Maybe someday only software designers who know how to use overloading will be allowed to call themselves designers and develop systems (does anybody really believes that? :P )
>The designer has to think, hard, about the method use. Once >the choice is made, that's it. The maintainers don't have >to worry about different versions. The clients don't have >to decide which version to use. Even the documentation >becomes shorter and easier to search. A whole area of >"cognitive overload" just vanishes and everyone can think >about something more important.
First, humans don't get it right the first time. Almost never. If a developer realizes that a method would be more useful if it had another parameter, he would have to invent another name, just because that? And if it was after shipping of his library? Should he just break compatibility, or add a new "betterMethod(a,b)"?
That is another function of overloading. To evolve an API keeping the meaning of the names of its operations, without breaking compatibility.
And sure, clients wouldn't have to decide which version to use. They would use another software, that shipped.
>OK, maybe I'm just dim. But I like things to be as simple >as possible.
As I've read somewhere,
"As simple as possible, but no simpler than that"
>Too many of the overloaded class designs around remind me >of the home handyman types who have ten different saws and >thirty types of screwdriver. Professionals seem to get by >with less, not more.
Professionals know how to use the tools they have, and have the tools they need. An amateur tennis player have one racquet, or many if he wants to show off. A professional one has one racquet to attack, another to defense, another to grass fields... You got the idea.
>Invoke "When I were a lad...": the most influential O-O GUI >system was probably the Xerox Parc Smalltalk system. The >most widely used would be Apple MacApp, Microsoft MFC, the >X Windows Toolkit, and today Apple Cocoa. All of them >succeeded without overloading (and only single inheritance >too) so based on the experience of those >programmer/architects it's tough for me to see overloading >as anything but a complication.
I don't know these toolkits, so I can't say much about them, but looking at the MFC API Reference, I see things like this:
HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam );
HWND CreateWindowEx( DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam );
Do I really have to use all those parameters just to create a window? They didn't have overloading (C), so they just put all the possible parameters to the default method. Besides that, they created another method CreateWindowEx(), with even more parameters, to extend the former's functionality. Overloading would be handy in this situation, would it? Or they should wait to ship the SDK until the designer got the "perfect design"? (that is, never)
Flat View: This topic has 15 replies
on 2 pages
[
12
|
»
]