December 07, 2005Humane or spartan?
I am not a big fan of YAGNI, and I have stated it in many occasions. In so doing, I am in disagreement with most of the XP crowd, including Martin Fowler, who says:
Interestingly, in a recent entry, Martin is now praising what is called a "humane interface":
Humane Interfaces clearly violate both YAGNI and the "do the simplest thing that could possibly work" principle, so I'm quite happy to see them gaining traction. For these same reasons, you can expect XP advocates to come out strongly against the idea of Humane Interfaces (and Elliote is leading the charge), which probably guarantees that they will become mainstream in no time :-) By the way, Elliote, nobody writes list.get(list.size() - 1) any more: we all have a method in a helper class somewhere and we invoke it everywhere we need it. Wouldn't it be nice if that method were included in the List class? Now, can we please have java.io.File#readFile() that reads an entire file in a String so we don't have to rewrite it over and over, like I just did this morning? Posted by cedric at December 7, 2005 01:22 PM Comments
I wonder... why do you have a utility class that has a method like ListUtilities.last(List) but no little util like FileUtilities.readFileContents(File)? And I agree: this readFile business is very annoying... also because you'll have to remember all the things that are necessary to make sure you do it right (make sure stream is closed in finally, what buffer size do you use, do you use BufferedReader.readLine and concat the Strings with a String Buffer or buffer yourself ...). Re: nobody writes Perhaps 'aCollection.iterator().next()' instead? Posted by: PA at December 7, 2005 02:45 PM> Perhaps 'aCollection.iterator().next()' instead? 1) Violation of encapsulation I think the broken part is needing the first or last element. It sounds to me like you should be using a Queue or a Stack for whatever it is you are doing, which likely why these were left out of the List API. I think that using that idiom every once in a while probably doesn't merit a whole method. If you go down this road too far you get the Common Lisp API which is just about as "humane" as you can get if thats what you want; I mean you might want the second or third or nth element right? Posted by: Sam Pullara at December 7, 2005 03:19 PMThe difference here is between "I *might* need this someday" and "Hundreds of people have coded this method, separately, on their own." One is speculation, the other is a path that has been worn into the grass. There is also a big difference between the design of a class that gets used millions of times (in a standard API) versus a class that gets used once. Unless you're amazingly good at predicting the future, I'd be willing to bet that you've added design-y generalizations, pluggable whatnots, etc. that have only been used once in the application. I know I have. (Great blog, btw!) Posted by: Mark at December 7, 2005 03:26 PMThe problem here is that a different context (writing public APIs to be used by hundreds of thousands of developers) changes the forces at play. When writing your own code, a Spartan interface is almost certainly better - less duplication, easier to see what's happening, and less time spent wondering "which one do I use"? YAGNI applies almost entirely to code written for your own use (or the use of your team or company). When writing code for others, it is a good idea to consider their common use cases and make it easier for them. This doesn't mean that you need to stick it all on the one class - the Collections and Arrays utility classes are a good example of providing "common use" methods. (Of course, you have to trade _this_ off the cost of learning the libraries) Almost by definition, when you write for others to use, you are speculating somewhat. YAGNI becomes less powerful, because you don't _know_ what others are going to need. The different context changes the strength of the various forces and thus the "best practice". The advice that Martin gives in the two entries quoted above is not contradictory, but applied to different contexts.
I'm not sure I agree with your assertion that Humane Interfaces necessarily violate YAGNI. YAGNI simply says don't implement functionality you don't need, yet; Humane Interfaces, IMHO, says if you need functionality then encapsulate that in an method on a class somewhere in a manner that enriches the domain rather than in some helper class. Or in other words, create high-level functionality rather than simply a set of primtive operations. I suppose you could think of this as being similar to CISC versus RISC. The question of whether or not you NEED the functionality or not is a separate discussion to HOW the functionality is implemented. YAGNI addresses the NEED, Humane Interfaces addresses the HOW. Posted by: Simon Harris at December 7, 2005 04:17 PMThis has nothing whatsoever to do with YAGNI. If you have at least one caller for each method in your "humane interface", and those callers are there because there's a business requirement for them, YAGNI is satisfied. (Extreme programming actually doesn't have a whole lot to say about how to design and evolve an external API. Its focus is on application development and internal API's, where backward compatibility at the API level is not a problem because you can always change the callers.) "Now, can we please have java.io.File#readFile() that reads an entire file in a String so we don't have to rewrite it over and over, like I just did this morning?" Commons-IO: +1 Brian Slesinsky's comment Martin's post on HumaneInterfaces has nothing to do with YAGNI. I am an XP fan, but I also think that Sun's anemic API's are a joke. There are so many commonly used methods missing from them that you have to really wonder what how stupid they really are. Anyway, YAGNI is good for app development but the philosophy has nothing whatsoever to do with generic APIs. Posted by: Marc at December 8, 2005 08:38 AMI've noticed that each version of Java seems to add a few "humane" methods here and there. Look at "String" for example and notice what wasn't there in the early Java versions but is there now. Posted by: LeeMeador at December 8, 2005 08:55 AMI find it funny that some people complain about the "minimalist" Java API. I find it funny that some people complain about the "bloated" Java API. I find it funny that often these are the same people. It's easy to pick up an API and complain about it. It's a lot more difficult to actually write that same API to make it useful. Choices have to be made as to what needs to be included or not. Sometimes bad choices are made (and this is true for ANY programming language and their APIs) and we have to live with it forever. The good thing about the minimalist approach is that you can easily add the functionality you want in an helper class or a subclass. With the humane approach, if it becomes bloated, you cannot take out the bloated parts without affecting compatibility. Now I think there is a middle ground between the minimalist and humane approaches. But the exact balance will never be reached in a single API. I like Java a lot... I begining to like Ruby a lot... I have gripes with APIs for both languages. Some Ruby APIs are better than their Java equivalent, and the other way around is also true. cost of maintenance makes baby jesus cry Posted by: Ron at December 8, 2005 10:42 AM(sorry cedric - it seems like its my monthly 'disagree with cedric' time again...) Code we (all of us) write falls into three categories: 1. Code you know you need We can all agree that you want code you need. We can all agree you don't need to write code you don't need. What we are talking about is code you might need. Why would you write code you only might need? If you might need it, you don't know you'll need it, so aren't you better off spending your time writing code (e.g. implemnenting functionality/deliverying business value) you know you'll need? If you have nothing left you know you need to do, why waste your time writing stuff you might need? Why not just ship/signoff the product/version/story and move on to the next thing? I see a lot of people missing the forest for the trees here. Libraries/classes in Ruby and Java exhibit their characteristics largely because of the characteristics of the _language_, not necessarily because of the designer. Some may consider it "humane" to have a wide interface to a class, providing a large number of methods (and quite often various permutations of parameters) in order to ease calling of that class. Wide interfaces create tension for implementors -- I wouldn't want to be the person having to implement 78 methods on an Ruby list just so my better-mousetrap can play nice with other parts of a system. What we all get annoyed at is having to type repetitive crap. We design "helper" functions into our classes and interfaces to fold the boilerplate together, and to clarify our thinking. Wide interfaces are a particular pain in the ass in Java, as inheriting common method implementations means that your overall inheritance structure is quite fixed (you need to start with AbstractList, for example). We can write the code to read a file into a string. Java and Ruby have us create a method somewhere that does this; we write a method call and have our String. What's better than calling a method? Not calling one at all. The wonderful world of type inferencing and views helps us out tremendously here. Consider this off-the-cuff example of Scala code: def view(file: java.io.File): String = { def makeElement(s: String): Element = { val elem1 = makeElement(new File ("c:/hello.world")); The powerful type system in Scala does extensive type inferencing, so it allows you to skip most declarations of type in your programs. Scala's _view_ system allows you to wire up a scope with explicit conversion functions; the appropriate function is automatically called by the compiler, as in the example above. Scala's bag of tricks also includes "traits", which are roughly like interfaces that can have code in them. The restriction is that traits cannot have _state_; they rely on classes to provide it. I don't consider Ruby (and other dynamically typed languages) to be particularly useful as they will _never_ (by definition) be able to perform type inferencing and view coercion. Dynamic languages will never be able to "do what I mean", when that meaning can be _reasoned_ out of the code, as written. Java could conceivably be extended to include some of these concepts. But why not simply use Scala? :) Posted by: Ross Judson at December 9, 2005 06:23 PMThe humane methods don't use the internals of the List implementation but are merely functions built on top of the List iterface. The humane classes can have varying implementations if so designed (e.g. sort()). Its not about mininal vs. humane - just where the methods belong. Most would agree that List should be mininal and the humane methods moved into a ListUtil. Just to reiterate with slightly different words: Once And Only Once, as well as expressive code, trump YAGNI. So if an XPer argues against humane interfaces, it's not because him being an XPer. Posted by: Ilja Preuß at December 17, 2005 01:08 AMOne should try not to take any one practice in isolation and try to evaluate its merits. In XP, while YAGNI is definitely a guiding principle, so are testing and refactoring. If during the course of writing your app code or tests, you came across the need to send the same object a message more than once, the correct response is to refactor and add it to that object. That way, the code is only introduced when needed (keeping with YAGNI) and humane interface (the method exists because it is needed more than once) and DRY (Don't Repeat Yourself). While you might think that this goes against "Simplest thing that could possibly work" - remember it has to be "simplest" not "laziest" or "first thing that comes to mind" or "copied from somehwhere where someone else had done exactly the same thing". The creation of additional methods is not in violation of "do the simplest thing" if the methods are introduced at the right time, when needed by tests. Posted by: badri at December 21, 2005 04:21 PMhi cedric, reading in entire file into a string by using 2 lines Scanner scanner = new Scanner(new File(fileName)); After I read this article(http://java.sun.com/developer/JDCTechTips/2004/tt1201.html#1) on java.net, I've been using these 2 lines for my work. BR, hi cedric, reading in entire file into a string by using 2 lines Scanner scanner = new Scanner(new File(fileName)); After I read this article(http://java.sun.com/developer/JDCTechTips/2004/tt1201.html#1) on java.net, I've been using these 2 lines for my work. BR, basketball betting this is madness! "I find it funny that some people complain about the "minimalist" Java API. I find it funny that some people complain about the "bloated" Java API. I find it funny that often these are the same people." They are shed painters who are too stupid to do anything else. Like this penny wise but pound foolish guy: "cost of maintenance makes baby jesus cry It costs Sun more to field feature enhancement requests for frequently used functionality, and bug reports stemming from user mistakes in implementing their own versions of that functionality, then it would to add it to the library in the first place. Post a comment
|