Do people provide multiple mechanisms for performing the same function in the API?

Is it difficult to design an API with several ways to achieve the same result? For example, I have my own Date library (which is a simple wrapper for the Java Date / Calendar classes to distinguish year-month-day, Date from time, Instant and provide mechanisms for converting between them). I started with one method to create an instance of Date :

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

But then I found that the resulting 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 began to speak fluently:

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

(I believe that the quick version is really useful for creating readable tests). All of these methods do the same and have an accurate JavaDoc. I think I read that the power of perl is that every programmer can choose exactly the method that he or she prefers to solve. And the power of Java is that there is usually only one way to do things :-)

What are people's opinions?

+4
source share
7 answers

I have been doing academic research for the past 10 years on various issues related to the usability of the Java API.

I can tell you that the statement that one way to do something in Java is pretty wrong. In Java, there are often many ways to do the same. And, unfortunately, they are often not consistent or documented.

One problem with inflating a class interface using convenient methods is that it becomes harder for you to understand the class and how to use it. The more choices you have, the harder it becomes.

When analyzing some open source libraries, I found instances of redundant functionality added by different people using different terms. Clearly, a bad idea.

The big problem is that the information conveyed by the name no longer makes sense. For example, things like putLayer vs. setLayer in swing, where one only updates the layer and the other also updates (guess which one?) is the problem. Similarly, getComponentAt and findComponentAt. In other ways, the more ways you can do something, the more you mess up the rest and reduce the "entropy" of existing functions.

Here is a good example. Suppose you want Java to replace a substring inside a string with another string. You can use String.replace (CharSequence, CharSequence), which works just as you expected, literal for a literal. Now suppose you wanted to make a regex replacement. You can use the Java Matcher and perform regular expression replacements, and any maintainer understands what you did. However, you can simply write String.replaceAll (String, String), which calls the version of Matcher. However, many of your companions may not be familiar with this and may not be aware of the consequences, including the fact that the replacement string cannot contain "$". Thus, replacing “USD” with “$” signs will work well with replace (), but will lead to crazy things with replacing All ().

Perhaps the biggest problem is that “doing the same thing” is rarely the problem of using the same method. In many places of the Java API (and I'm sure that in other languages) you will find ways to do "almost the same thing", but with differences in performance, synchronization, state changes, exception handling, etc. For example, one call will work directly, and the other will set locks, and the other will change the type of exception, etc. This is the recipe for the problem.

So the bottom line. A few ways to do the same is not a good idea if they are not straightforward and very simple, and you take great care to ensure consistency.

+7
source

I would repeat what some others said in this convenient method, but it will take one more step - all the “convenient” methods should eventually call the same base method . The only thing that convenience methods should do other than the proxy is to accept or return variables in different ways.

There are no calculations or processing in convenience methods. If you need to add additional features to one of them, go the extra mile and do it in the "main" / "real".

+5
source

Well, to provide convenient methods, the real problem is that each entry point starts to behave differently. Thats when the api is no longer convenient. Its just a pain to remember which path is "right" and the documentation starts saying "recommended way ..."

If Date.yearMonthDay () started checking the date, and Date.ymd () did not, then this will be a problem. The same thing happens if everyone starts to support different "functions" - Date.yearMonthDay () can accept non-gergy dates, and Date.date () can accept non-gregorian dates, as long as a 4th object is given that tells the calendar the type.

+4
source

First, please do not invent your own date library. Too hard to get right. If you have absolutely nothing to do, be sure to read - and understand - Calendar calculations . Without an understanding of calendar calculations, you run the risk of doing something wrong in obscure cornerstones and marginal cases.

Secondly, multiple access to a common base method is typical. Many Java library API methods declare that they are just a “wrapper” around some other class method.

In addition, due to Java language limitations, you often overload method names as a way of providing “optional” arguments to a method.

Several access methods are great designs.

+3
source

If they do the same:

  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 this is a bad form. When I read your code, I don’t know which one to use.

Such things as

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

different in that it allows the caller to access functions without having to determine how to format the data.

+3
source

My personal opinion is that you should stick to one method in order to do something. All 4 methods ultimately invoke the same method, after which you only need one of them. If, however, they do something other than calling a method, then they must 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 methide in addition to the cursory version will make more sense. But the methods yearMonthDay, ymd and date should go.

In addition, different languages ​​have different goals. Just because it makes sense in Perl doesn't mean it makes sense in Java (either C #, or C ++, or C, or Basic, or ...)

+1
source

I find the quick version really useful for creating readable tests.

This is a bit troublesome because I worry that you can only test the free version. If there is only one reason why the X () method exists, then you can have a readable test for the Y () method, then there is no reason for one of the methodX () or methodY () methods. You still need to test them in isolation. You are reluctant to repeat yourself.

One of the main principles of TDD is that you force yourself to think about your API while writing code. Determine which method you want to use for your API clients and get rid of redundant ones. Users will not thank you for providing convenient methods, they will curse you for cluttering up your API with seemingly useless redundant methods.

+1
source

All Articles