Java News from Thursday, December 8, 2005

The discussion of humane interfaces continues in various and sundry places. I suspect there's more middle ground between Fowler and me than perhaps some people realize. His essential point is "The essence of the humane interface is to find out what people want to do and design the interface so that it's really easy to do the common case." I don't disagree with the principle. However we part ways on the practical application of that theory to class design. In particular I believe:

There's been some push back on the specific case of the first() and last() methods Fowler cites. Some people like them. I didn't understand why since I almost never use any such method. However, several people noted that these are useful for queues and stacks; but if you're using a queue or a stack, shouldn't you use a Queue or Stack class rather a List class? (Yes, I know Java's Stack class is an abomination. No, that doesn't affect my basic point.) But let's grant the first() and last() methods for moment, and look at what else you'll find in Ruby's 78 method Array class.

Array.new(size=0, obj=nil)

This method creates an array that has size copies of the object. Why? Have you ever needed to create an array/list that begins its life containing more than one copy of the same object? I don't think I have. I can imagine a few uses cases, especially if Ruby doesn't distinguish primitive types from objects like Java does; but I don't think they're nearly common enough to justify explicit support in the API. On the rare occasions when you need this, there's a fill method you can call after constructing the list.

array * int -> an_array

"Returns a new array built by concatenating the int copies of self." I've thought and I've thought, and I still can't figure out when you might need this.

array <=> other_array -1, 0, +1

A comparison between arrays. However, without me stating it here, can you understand how to tell whether two arrays are greater than, equal to, or less than each other? There's more than one possibility here. This needs to be pulled out into a separate interface that allows for different comparison algorithms. Plus how often do you need to compare lists for anything but equality anyway? Even that's uncommon. I'll leave it as an exercise for the reader to read the documentation for this method and find the underspecification. It is possible for implementation A to conclude that array foo is less than array bar and implementation B to conclude the exact opposite. These things happen when classes get so big that even the programmers who write the specs don't have time to fully understand them.

abbrev(pattern = nil)

"Calculates the set of unambiguous abbreviations for the strings in self." Excuse me. What?! This one's totally out of left field. Does it even make sense if the list contains anything other than strings? If someone needs this, it should be in a separate class. The list class cannot hold every operation anyone might ever want do to a list. If we try that, why not a method to capitalize all the strings in the list? Or spell check them? Or translate the entire list into Swahili? The list of things you might want to do is endless, but the list class shouldn't be.

array.compact! -> array or nil

"Removes nil elements from array. Returns nil if no changes were made." Compacting a list seems useful if a little uncommon. But compacting an array and sometimes returning nil? Does anyone ever use this? There's also a perfectly good method to compact an array and return it, without ever returning nil. That's a lot more useful. I only care whether the list now contains any nil objects or not, not whether it once contained nil objects.

array.each_index {|index| block } -> array

"Same as Array#each, but passes the index of the element instead of the element itself." Am I missing something, or is this just a for loop across the array indexes? Why not just use a for loop, or whatever Ruby's equivalent is?

array.at(index) -> obj or nil

As near as I can figure, this is exactly the same as using array brackets except it uses a method call. If there is a difference, it's pretty subtle. DRY, anyone?

array.fetch(index) -> obj

This one is mostly the same as array brackets, but there is one difference. It will throw an IndexErr rather than returning nil if you go outside the range. But will anyone remember that difference and which does which when reading code? Sometimes when presented with different possible options, it's best just to pick one. Don't provide every possible option in an API.

array.nitems -> int

"Returns the number of non-nil elements in self. May be zero." Another one it never even occurred to me I might need. I suspect this one's beyond the 90/10 point. heck. It's beyond the 99/1 point.

arr.pack ( aTemplateString ) -> aBinaryString

I'm not even going to try to explain this one. This method alone is easily complex enough to justify several separate classes. What it's doing here I have no idea.

array.pop and array.push(obj, ... )

Someone pushed a stack into the list. Didn't we learn from that mistake in Java? (OK. push is really just append, but still, pop? And if you have pop why do you need last? Does anyone here remember DRY?)

array.rassoc(key)

Is it a list? Is it a stack? Is it a map? It's three, three, three data strcutures in one!

array.shift and array.unshift(obj, ...)

Now the list has turned into a queue!

array.transpose --> an_array

And now it's a matrix! What's next? a B-tree?

array.reverse and array.reverse!

Have you ever needed to reverse a list outside of a job interview? Iterate through the list in reverse order, yes; reverse the list, no.

size()

Exactly the same as length(). This class is looking pretty WET.

array.to_a -> array

"Returns self. If called on a subclass of Array, converts the receiver to an Array object." Is Ruby not object oriented? Is there some deep, dark reason we can't just use an instance of the subclass as an instance of the superclass?

array.zip(arg, ...)

I don't want to pick on programmers who are not native English speakers, and Lord knows Java's API documentation is not a model of good English; but perhaps if this class weren't so damn large, someone who is a native English speaker might have read to the end of this page once or twice and noticed the multiple typos in the description of this method? (I don't think it's a coincidence that the grammar gets worse the closer to the end of the page you get.)

Do you really need any of this getting in your way? Possibly you need one or two of these methods; but how often, and how many? These methods are all way beyond the 80/20 point. I promised today I was going to show you all the methods you could take out of the Array class and still have a humane, indeed a more humane interface. I'm afraid I haven't fulfilled my promise. I got tired before I got halfway through the class and started skipping some cases. Try reading the entire documentation yourself from start to finish, and you'll see what I mean. It's just too damn big. A smaller, simpler API would be more humane.


The Gnu Project has released version 3.4.5 of GCC, the GNU Compiler Collection. GCC contains front ends for C, C++, Objective C, Chill, Fortran, Ada, and Java as well as libraries for these languages. GCC is a clean room implementation of Java that doesn't use any Sun code, so it doesn't always exactly match Sun release versions, but this is roughly at the Java 1.4 level with some omissions. 3.4.5 is a bug fix release.