tags:

views:

197

answers:

7

Is it confusing to design an API with multiple ways of achieving the same outcome? For example, I have my own Date library (which is a simple wrapper around the Java Date/Calendar classes to distinguish a year-month-day, Date, from an instant-in-time, Instant and provide mechanisms to convert between the two). I started off with one method to create an instance of a Date:

Date.valueOfYearMonthDay(int year, int month, int day);

But then I found that the resultant code using the API was not very readable. So I added:

Date.yearMonthDay(int year, int month, int day)
Date.ymd(int year, int month, int day)
Date.date(int year, int month, int day)

Then I started getting fluent:

Date.january().the(int day).in(int year);

(I find that the fluent version is really useful for making readable tests). All these methods do identical things and have accurate JavaDoc. I think I've read that a strength of perl is that each programmer can choose exactly which method he/she prefers to solve something. And a strength of Java is that there is usually only one way of doing things :-)

What are people's opinions?

+4  A: 

Its fine to provide convenience methods, the real problem is if each entry point begins to do behave in subtly different ways. Thats when the api isn't convenient anymore. Its just a pain to remember which way is "right," and documentation starts saying "the recommended way is..."

If Date.yearMonthDay() began to validate the date while Date.ymd() didn't, that'd be a problem. The same goes for if each begins supporting different "features" - Date.yearMonthDay() could take non-gregorian dates, and Date.date() could take a non-gregorian dates so long as a 4th object is given that tells the calendar type.

Richard Levasseur
They are all identical; I've edited the post to that effect. It's a good answer - I'll up-vote you in 4 hours when I get my daily ration :-)
oxbow_lakes
+1  A: 

My personal opinion is that you should stick with one method to do something. It all 4 methods ultimatly call the same method then you only need on of them. If however they do something in addition to calling them method then they should exist.

so:

// This method should not exist
Data yearMonthDay(final int year, final int month, final int day)
{
    return (valueOfYearMonthDay(year, month, day));
}

The first methid in addition to the fluent version would make more sense. But the yearMonthDay, ymd, and date methods should go.

Also, differnt langauges have different goals. Just because it makse "sense" in Perl doesn't mean it makes sense in Java (or C#, or C++, or C, or Basic, or...)

TofuBeer
Agreed; personally I found Ruby confusing as actual language constructs could be achieved in multiple ways (blocks/yield etc).
oxbow_lakes
+3  A: 

First, please don't invent your own date library. It's too hard to get right. If you absolutely have nothing better to do, be sure to read -- and understand -- Calendrical Calculations. Without understanding Calendrical Calculations you run a big risk of doing things wrong in obscure corner and edge cases.

Second, multiple access to a common underlying method is typical. Lots of Java library API methods state that they are simply a "wrapper" around some other method of class.

Also, because of the Java language limitations, you often have overloaded method names as a way to provide "optional" arguments to a method.

Multiple access methods is a fine design.

S.Lott
My date library does no underlying date calculations at all. It is simply a wrapper around the Java Date/Calendar API which makes a distinction between a year-month-day and an instant in time. It's perfect for what I need and is not meant (like JODA) to be a full-on alternative to the Java classes
oxbow_lakes
@Chris Marshall: Please edit your question to include these new facts.
S.Lott
Ive found myself doing the very same thing - after like 15 iterations of going from new Date().getTime() to Calendar.setTime() and back again you get really compelled to just make your own class that does all the boilerplate for you...
sweeney
@S.Lott - I've made the edits. Not sure they're actually relevant to the question I'm actually asking tho :-)
oxbow_lakes
@Chris Marshall: Notice that your implementation of Date library distracted me from your real question. If you don't clarify your questions, you get unclear answers. If it's not relevant to the question, consider deleting it.
S.Lott
+5  A: 

I'd echo what some others said in that convenience methods are great, but will take it a step further - all "convenience" methods should eventually call the same underlying method. The only thing that the convenience methods should do other than proxy the request is to take or return variables differently.

No calculations or processing allowed in the convenience methods. If you need to add additional functionality in one of them, go the extra mile and make it happen in the "main" / "real" one.

routeNpingme
+1  A: 

I find that the fluent version is really useful for making readable tests.

This is a little bit troublesome because I worry that you might only be testing the fluent version. If the only reason methodX() exists is so you can have a readable test for methodY() then there is no reason for one of methodX() or methodY() to exist. You still need to test them in isolation. You're repeating yourself needlessly.

One of the guiding principles of TDD is that you force yourself into thinking about your API while you're writing your code. Decide which method you want clients of your API to use and get rid of the redundant ones. Users won't thank you providing convenience methods, they'll curse you for cluttering your API with seemingly useless redundant methods.

Bill the Lizard
I see your point. But my tests aren't testing the creation factories; they're testing the dates which those factories return. Or they might not even be tests of Date at all; but another class which needs a date in some way.
oxbow_lakes
+7  A: 

I've been doing academic research for the past 10 years on different issues that have to do with API usability in Java.

I can tell you that the statement about having one way to do things in Java is fairly incorrect. There are often many ways to do the same thing in Java. And unfortunately, they are often not consistent or documented.

One problem with bloating the interface of a class with convenience methods is that you are making it more difficult to understand the class and how to use it. The more choices you have, things become more complex.

In an analysis of some open-source libraries, I've found instances of redundant functionality, added by different individuals using different terms. Clearly a bad idea.

A greater problem is that the information carried by a name is no longer meaningful. For example, things like putLayer vs. setLayer in swing, where one just updates the layer and the other also refreshes (guess which one?) are a problem. Similarly, getComponentAt and findComponentAt. In other ways, the more ways to do something, the more you obfuscate everything else and reduce the "entropy" of your existing functionality.

Here is a good example. Suppose you want in Java to replace a substring inside a string with another string. You can use String.replace(CharSequence, CharSequence) which works perfectly as you'd expect, literal for literal. Now suppose you wanted to do a regular expression replacement. You could use Java's Matcher and do a regular expression based replacement, and any maintainer would understand what you did. However, you could just write String.replaceAll(String, String), which calls the Matcher version. However, many of your maintainers might not be familiar with this, and not realize the consequences, including the fact that the replacement string cannot contains "$"s. So, the replacement of "USD" with "$" signs would work well with replace(), but would cause crazy things with replaceAll().

Perhaps the greatest problem, however, is that "doing the same thing" is rarely an issue of using the same method. In many places in Java APIs (and I am sure that also in other languages) you would find ways of doing "almost the same thing", but with differences in performance, synchronization, state changes, exception handling, etc. For instance, one call would work straight, while another would establish locks, and another will change the exception type, etc. This is a recipe for trouble.

So bottom line: Multiple ways to do the same thing are not a good idea, unless they are unambiguous and very simple and you take a lot of care to ensure consistency.

Uri
Great answer. I think (in defence of the String replace example) that the addition of functionality when new features (i.e. regex) are added to the core APIs is a good thing. But you are correct of course that this may add confusion unless very clearly documented
oxbow_lakes
+3  A: 

If these do the exact same thing:

 Date.yearMonthDay(int year, int month, int day)
    Date.ymd(int year, int month, int day)
    Date.date(int year, int month, int day)

I think that is bad form. When I am reading your code, I have no clue which one to use.

Things like

canvas.setClipRegion (int left, int top, int right, int bottom); 
canvas.setClipRegion (Rect r);

are different in that it allows the caller to access the functionality without having to figure out how to format the data.

hacken
Well, the JavaDoc on all 3 methods says exactly what they do and that they are all identical. I leave it up to the user to decide which style they like best
oxbow_lakes
You are increasing the amount of information that someone reading your code needs to remember. What does flexibility does ymd (less typing?) or date(?) provide that yearMonthDay doesn't provide? Would you give a member variable 3 names and allow a developer to choose which one to use?
hacken