 |
Humane interfaces, simplisticity, and domain languages
Posted by johnm on December 07, 2005 at 01:39 PM | Comments (17)
Elliotte Rusty Harold
has touched off a small war in his response to Martin Fowler's recent entry on so-called Humane Interfaces.
One facet of the debate is an example comparing the equivalent "List" classes from Ruby (Array) and Java (java.util.List). Java's list class has 25 methods while Ruby's has 78 methods. Martin uses that fact to conclude that Ruby's list class is somehow more "humane" while Elliotte's thinks it's just bloated and that a minimal interface is better in terms of how people work.
Martin's primary argument for the "more is better" approach is that:
Humane interfaces do more work so that clients don't have to. In particular the human users of the API need things so that their common tasks are easy to do - both for reading and writing.
while Elliotte's "less is more" approach is that:
More buttons/methods does not make an object more powerful or more humane, quite the opposite in fact. Simplicity is a virtue. Smaller is better.
As you might have guessed, I think both of them are partially right and that there's something even more important that they are really bringing up that should be discussed.
For example, list.first() is actually more humane/usable/readable/etc. than list.get(0). Why? Because the intent needs to be clear and obvious to humans, not just the compiler. Even worse, crap like list.get(list.size() - 1) is just plain wretched compared to list.last() -- intent, clarity, complicatedness, easy to get wrong (off by one), etc. Also, look at how many parts of the list abstraction are "leaked" in just those two examples such as linear positioning, indexing, zero based indices, the first element is "always" at position zero, reliable sizing, etc.
However, Elliotte is completely right that having 78 methods in any class is an atrocity. Something that has that much surface area is way too complicated for humans to keep manageable. In addition, it also sets a bad example for coders learning the recommended ways of doing things -- i.e., "just throw anything you feel like in there."
Going to the opposite extreme of a bare minimum, necessary set of methods is also too simplistic. For example, Elliot throws downs an image of various remote controls, two fairly complicated "universal" remotes and a minimalistic one from Apple. But, who gets to chose what that minimal set will be for everybody? In software, almost everyone will end up wasting time and introduce bugs by writing their own versions of truly common bits of code. Software is, in this regard, much more of an engineering practice than a mathematical reduction.
Hopefully it's obvious that there's a reasonable middle ground. Like any good standard, deciding on what to put into the core library should be about codifying truly common behavior rather than what might sound good to any one special interest group. Other important tools in this effort are things like good design principles and refactoring. [I find it ironic that Elliot brought up the issue of refactoring in a debate with Martin.] Also, the both extremes miss out the addressing the specific needs of the context in which the code is being used: a pro using something everyday vs. a serious hobbyist vs. a random user vs. a half-blind grandmother with rheumatoid arthritis vs. .... Context matters.
Alas, arguing back and forth over those sorts of details makes it easy to miss a fundamental, crucial point: no software (library, application, language, operating system, or whatever) can be all things to all people. Fighting that war is not only pointless but is one of my definitions of insanity. The point of a chunk of good software is to enable the effective and efficient creation of more good software and to help inhibit the creation of bad software.
So, how do we then build up our own code to fix whatever shortcomings we find? Build our own libraries on top of whatever the core gives us which provide the clean abstractions and domain specific languages to get our jobs done, well.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
It reminds me of a language, I don't remember which one, where you could access list elements using not only first and last, but also second, third, fourth, etc. to ninth. Eliott makes a really good point when asking how many times we really need to access the first and the last element of a list? If you do, you probably are really using a Queue or a Stack (which are list but still).
Having used scripting languages like Ruby for years I really don't understand why people are suddenly going crazy with it all over the web. Heck, good easy to use web frameworks even existed before Rails. I guess I'm missing something important here. Anyway, while I don't care at all about things like first or last, I do care about feature like Python's range indexing (list[2:6]) or negative indexing (last is in fact list[-1]). So I do agree with you, no software is all things for all people. And Ruby certainly isn't for me.
Posted by: gfx on December 07, 2005 at 02:16 PM
-
It reminds me of a language, I don't remember which one, where you could access list elements using not only first and last, but also second, third, fourth, etc. to ninth.
I'm not sure which particular language you're thinking of but certainly Lisp and Scheme have support for car & cdr to some ridiculous depth. E.g., you can write caddadaddr. :-)
Eliott makes a really good point when asking how many times we really need to access the first and the last element of a list? If you do, you probably are really using a Queue or a Stack (which are list but still).
Indeed. In other words, one uses (or creates) an appropriate abstraction/"language" which says what you mean and means what you say.
Having used scripting languages like Ruby for years I really don't understand why people are suddenly going crazy with it all over the web.
That's for sure. Part of this is the "tipping point" / "hundreth monkey" effect. Part of this is that the software development business is slowly growing up. Part of the freight train of Ruby is really all of the hype around Rails -- especially coming along at the time when people are in full flight away from the heavyweight insanity of the side of J2EE.
Posted by: johnm on December 07, 2005 at 09:02 PM
-
Here's a couple of other takes on this general topic:
Posted by: johnm on December 07, 2005 at 09:31 PM
-
Indeed, as you said "no software (library, application, language, operating system, or whatever) can be all things to all people". This means humane interface for one kind of application could be nuisance for another.
There is an interesting twist to creating humane interface when you throw in AOP into the picture. With aspects, you can introduce methods into the interface without modifying the interface. You can write as many aspects, each contributing a part of humane interface, as you see fit. This allows each application to include just the right set of aspects and thus create application-specific humane interface.
See Creating humane interfaces using AspectJ for more details and examples using this approach.
Posted by: ramnivas on December 07, 2005 at 09:40 PM
-
James Robertson also blogged his response to my argument that:
However, Elliotte is completely right that having 78 methods in any class is an atrocity. Something that has that much surface area is way too complicated for humans to keep manageable. In addition, it also sets a bad example for coders learning the recommended ways of doing things -- i.e., "just throw anything you feel like in there."
which James claims is a bald assertion. Hm... Kind of odd how he claims that I made a bald assertion when he even quoted that complete paragraph in his response. My argument is based on plenty of evidence that such large surface areas are too complicated. As that wasn't the core argument of my piece, I haven't bothered to link to any other sites that might be helpful as I find it hard to believe that somebody reading this blog (a) hasn't been exposed to such evidence already and (b) is able to do a little homework to find plenty of evidence on their own when I did provide various hints. Heck, that includes the very fact that Martin Fowler is famous for his seminal book on refactoring which talks quite a bit about this sort of "code smell". Alas.
Posted by: johnm on December 07, 2005 at 09:58 PM
-
Note that I also added a comment to James' response that has some further explaination.
Posted by: johnm on December 07, 2005 at 10:22 PM
-
Elliotte continues with some clarification of his position and a lot of examples of fluff from the "humane" API.
Posted by: johnm on December 08, 2005 at 06:53 AM
-
ramnivas, aspects are another very slippery slope. Where do clean aspects like tracing/logging give way to spaghetti aspects? In fact, with aspects, their hidden nature (from e.g., the original code) makes it very easy to increase the overall complexity of the system. Many of the downsides of aspect abuse are the same as general operator overloading ala C++.
Posted by: johnm on December 08, 2005 at 09:27 AM
-
John, aspects can be used very effectively to simplify application code. Granted, there can be overuses and misuses, but that is possible with any technology (including OO).
Anyway, back to the original issue of implementing humane interfaces with aspects. As I note in my blog, you can put humanizing aspects close to the core interface itself (as either nested aspects or peer same-source aspects). Then aspects are no longer "hidden". If you use near-the-source style, you will get compartmentalization of convenience methods (much like Smalltalk method category feature), instead of full separation. Depending on the context, I have used both styles.
Posted by: ramnivas on December 08, 2005 at 11:30 AM
-
Groovy has support for method
categorieswhich seems to be a form of AOP and the groovy compiler generates standard Java bytecode.
Posted by: radhamohan on December 09, 2005 at 05:23 AM
-
romain/gfx:
You're thinking of Common Lisp, which provides many of those methods (first, second, third, etc) as macros which expand out to normal list access. That sort of thing is normal Common Lisp style - compare that to another Lisp family language, Scheme, which is for the "less is more" crowd.
Anyway, have these people ever heard of Jef Raskin? I'm pretty sure he was using the term "humane interface" (even writing a book with that title) before all this, and it sure didn't have anything to do with API complexity.
Posted by: rlinwood on December 09, 2005 at 07:15 AM
-
ramnivas, yes, there are some examples of formal aspects effectively simplifying code. [People have been doing "informal" and manual aspects for decades.] Alas, the separateness of aspects from core code brings about a lot of risks (just like operator overloading). That separateness brings a certain additional power but also brings new problems for people trying to understand how a system really works, debugging, etc. because things aren't necessarily clearer just because they are separate. In terms of this thread, aspects make it easier to create inhumane systems than to make simpler systems. That asymmetry isn't a priori bad, it's just a fact that we have to deal with. That's why we get the big bucks, right? :-)
Posted by: johnm on December 09, 2005 at 08:50 AM
-
rlinwood, yep, Jef Raskin certainly popularized the notion of "humane" interfaces in terms of e.g., GUIs. However, an API is literally just another type of interface. All of the stuff that he talked about in terms of GUIs applies analogously if not exactly to APIs.
Posted by: johnm on December 09, 2005 at 08:57 AM
-
John, very similar issues were raised with OO too (and I am sure any methodology or technology, for that matter). After all, each methodology offers a separation of a kind.
BTW, you should take a look at AspectJ's eclipse plugin -- AJDT. This plugin makes debugging with aspects just as natural as with plain Java. 'You can set breakpoints in methods or advice. If you set a breakpoint in a method, and there is an advice applicable there (which are clearly indicated in gutter annotations), and then you "step into", debugger take you to the advice.
Posted by: ramnivas on December 09, 2005 at 09:11 AM
-
ramnivas, yes, exactly the point. And if you look at the history of OO, people way over-hyped the benefits and way over-discounted the downsides. Again, as is my main point in this thread, the point is not that any one extreme is better or worse than another but that we have to figure out how and where the appropriate middle grounds are.
Posted by: johnm on December 09, 2005 at 09:46 AM
-
John, I agree! In fact, during my talks, I emphasize that it is best to gradually adopt AOP. This way, you get time to learn nuances of it and manage risk with in adopting a new technology.
Posted by: ramnivas on December 09, 2005 at 10:57 AM
-
java.util.List has the bare minimum. java.util.ListUtil contains the other stuff, maybe as static methods. Thats how I would to it.
Posted by: herkules on December 11, 2005 at 02:41 AM
|