The Artima Developer Community
Sponsored Link

Weblogs Forum
Java API Design Guidelines

34 replies on 3 pages. Most recent reply: Jan 5, 2006 2:32 PM by Geek Alot

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 34 replies on 3 pages [ 1 2 3 | » ]
Eamonn McManus

Posts: 20
Nickname: dichotomy
Registered: Apr, 2003

Java API Design Guidelines (View in Weblogs)
Posted: Dec 28, 2005 8:04 AM
Reply to this message Reply
Summary
There are tons of books and articles about how to design and write good Java code, but surprisingly little about the specific topic of API design. Here's a summary of what I've learnt on the subject from various sources and my own experience.
Advertisement

I recently attended an excellent talk at JavaPolis, Elliotte Rusty Harold's XOM Design Principles. Although the talk is nominally about XOM (an API for XML documentation manipulation), in fact more than half of it is about API design principles in general. This is a curiously neglected subject. There are tons of books and articles about how to design and write good Java code, but surprisingly little about the specific topic of API design. Yet with the proliferation of new Java APIs, whether through JSRs or through Open Source projects, this is an increasingly important subject.

I've been closely involved with the evolution of the JMX API for over five years and have learnt a great deal about what works and what doesn't during that time. During the talk, I had the odd experience of continually wanting to cheer as Elliotte made point after point that I hugely agreed with.

I'm going to try to summarize here what I see as being the key points from this talk, from my own experience, and from a couple of other sources:

[Update: Although I was unaware of it when writing this blog entry, the slides referenced by Josh Bloch in a comment here cover some of the same ground and add much of interest.]

Design to evolve

If your API is worth anything, it will evolve over time. You should plan for this from the outset. A key part of the planning is to decide what sort of compatibility you will guarantee between revisions.

The best approach is to say that once something is in the API it will stay there and it will continue to work. Tweaking the API incompatibly between revisions will result in user reactions ranging from annoyance to murderous rage. The problem is particularly severe if your API ends up being used by different modules that are part of the same application. If Module 1 uses Commons Banana 1.0 and Module 2 uses Commons Banana 2.0 then life will be a whole lot easier if 2.0 is completely compatible with 1.0. Otherwise your users risk wasting huge amounts of time tweaking classpaths in a futile endeavour to make things work. They might end up having to play mind-destroying games with class-loaders, which is a clear signal that you have failed.

For APIs that are part of Java SE, we have an extreme form of compatibility. The aim is that no code whatsoever should break when you update from one version to the next. This means that classes and methods are never removed. It also means that we try to avoid changes that might break code that was depending on certain implementation details, even if the code shouldn't have been doing that.

The no-code-breakage rule applies to already-compiled code (binary compatibility). In some rare circumstances we might make changes that mean some existing code no longer compiles (source compatibility). For example, adding an overloaded method or constructor can sometimes produce ambiguity errors from the compiler when a parameter is null. We do try to find a way to avoid changes that break source compatibility in this way, but sometimes the best approach does imply that some source code might stop compiling. As an example, in Java SE 6 the constructors for javax.management.StandardMBean have been generified. Some existing source code might conceivably stop compiling because it does not respect the constraints that are expressed using generics here, but that code is easily fixed by adding a cast, and the rare cases where that happens are outweighed by cases where the constraints will catch programming errors at compile time.

In general, you can't know what users of your API will do with it. When contemplating a change that might break existing code, you have to reason conservatively. Only if you can honestly say that it is next to impossible that a change will break code can you reasonably make it. You should certainly rule out completely a signature change, which basically means removing or renaming a visible method or class or changing the parameters of a visible method. (But you can remove a method if it overrides a method in a parent class without changing the parent method's semantics.)

Since the very earliest versions of your API are sure to have many mistakes in them, and you don't want to freeze those mistakes for all time, it's a good idea to bring out one or more 0.x versions before the 1.0 version. Users of these versions know that the API is unstable and won't curse you if it changes. Once you've brought out 1.0 you're committing to compatibility. For APIs that are developed through the JCP, these 0.x versions correspond to the phases before the final release (Early Draft Review, Public Review, Proposed Final Draft). If possible, it's a good idea to make an implementation of the API available at the same time as these intermediate specifications.

If at some stage you decide that there's really too much accumulated cruft from previous versions and you want to start over, then create a new API with different package names. Then code that uses the old version and code that uses the new version can co-exist easily.

API design goals

What should the design goals of your API be? Apart from compatibility, the following goals from Elliotte's presentation seem like an excellent set:

  • It must be absolutely correct. In the case of XOM, this meant that the API could never produce malformed XML documents no matter what the caller did. For the JMX API, for example, it means that you can never get the MBean Server into an inconsistent state by registering strange MBeans in it or using funny ObjectNames or performing several operations concurrently.
  • It must be easy to use. This is hard to quantify. A good way to get an idea is to write lots of example code. Are there groups of operations that you keep having to repeat? Do you have to keep looking up your own API because you forget what things are called? Are there cases where the API doesn't do what you might expect?
  • It must be easy to learn. This overlaps considerably with ease of use. But there are some obvious principles to make learning easier. The smaller the API, the less there is to learn. Documentation should include examples. Where appropriate, the API should look like familiar APIs.
  • It must be fast enough. Elliotte was careful to put this in the list after the above items. Make sure the API is simple and correct. Then think about performance. You might be inclined to make API changes because the original API could only be implemented in an inefficient way. By all means change it to allow a more efficient implementation, provided you don't compromise correctness or simplicity. Don't rely on your intuition to know what performs well. Measure. Then tweak the API if you've determined that it really matters.
  • It must be small enough. This covers the size of the compiled code and especially the amount of memory it needs as it runs. The same principles as for speed apply. Make it simple and correct first; measure; and only then think about tweaking the API.

Be minimalist

Because of the compatibility requirement, it's much easier to put things in than to take them out. So don't add anything to the API that you're not sure you need.

There's an approach to API design which you see depressingly often. Think of everything a user could possibly want to do with the API and add a method for it. Toss in protected methods so users can subclass to tweak every aspect of your implementation. Why is this bad?

  • The more stuff there is in the API, the harder it is to learn. Which classes and methods are the important ones? Which of the five different ways to do what I need is the best?

    The situation is exacerbated by the Javadoc tool, which dumps all the classes in a package, and all the methods in a class, in an undifferentiated lump. We can expect that JSR 260 will update the Javadoc tool to allow you to produce "views" of the API, and in that case fatter APIs will not be so overwhelming.

  • The bigger the API, the more things can go wrong. The implementation isn't going to be perfect, but the same investment in coding and testing will yield better results for a smaller API.

  • If your API has more methods than it needs, then it's taking up more space than it needs.

The right approach is to base the API on example code. Think of problems a user might want to solve with the API. Add just enough classes and methods to solve those problems. Code the solutions. Remove anything from the API that your examples don't need. This allows you to check that the API is useful. As a happy side-effect, it gives you some basic tests. And you can (and should) share the examples with your users.

Interfaces are overvalued

There's a certain style of API design that's very popular in the Java world, where everything is expressed in terms of Java interfaces (as opposed to classes). Interfaces have their place, but it is basically never a good idea for an entire API to be expressed in terms of them. A type should only be an interface if you have a good reason for it to be. Here's why:

  • Interfaces can be implemented by anybody. Suppose String were an interface. Then you could never be sure that a String you got from somewhere obeyed the semantics you expect: it is immutable; its hashCode() is computed in a certain way; its length is never negative; and so on. Code that used String, whether user code or code from the rest of the J2SE platform, would have to go to enormous lengths to ensure it was robust in the face of String implementations that were accidentally incorrect. And to even further lengths to ensure that its security could not be compromised by deliberately evil String implementations.

    In practice, implementations of APIs that are defined entirely in terms of interfaces often end up cheating and casting objects to the non-public implementation class. DOM typically does this for example. So you can't give your own implementation of the DocumentType interface as a parameter to DOMImplementation.createDocument and expect it to work. Then what's the point in having interfaces?

  • Interfaces cannot have constructors or static methods. If you need an instance of an interface, you either have to implement it yourself, or you have to ask some other object for it. If Integer were an interface, then to get the Integer for a given int you could no longer use the obvious new Integer(n) (or, less obvious but still documented inside Integer, Integer.valueOf(n)). You would have to use IntegerFactory.newInteger(n) or whatever. This makes your API harder to understand and use.

  • Interfaces cannot evolve. Suppose you add a new method to an interface in version 2 of your API. Then user code that implemented the interface in version 1 will no longer compile because it doesn't implement the new method. You can still preserve binary compatibility by catching AbstractMethodError around calls to the new method but that is clunky. If you use an abstract class instead of an interface you don't have this problem. If you tell users not to implement the interface then you don't have this problem either, but then why is it an interface?

  • Interfaces cannot be serialized. Java serialization has its problems, but you can't always get away from it. The JMX API relies heavily on serialization, for example. For better or worse, the way serialization works is that the name of the actual implementation class is serialized, and an instance of that exact same class is reconstructed at deserialization. If the implementation class is not a public class in your API, then you won't interoperate with other implementations of your API, and it will be very hard for you to ensure that you even interoperate between different versions of your own implementation. If the implementation class is a public class in your API, then do you really need the interface as well?

Of course, there are sometimes good reasons for a type to be interface. Here are some common ones:

  • Callbacks. If the interface is intended to be implemented by user code, then it is often more appropriate than an abstract class. See Runnable for example. This is mostly true of interfaces with just one method. Once there start being several methods you often find that an implementation class only needs to do something in one of them, and it's annoying to have to implement all the others. Furthermore if an interface has three methods today then you might want it to have four tomorrow, which is not usually possible as we saw. An abstract class can avoid these problems.

  • Multiple inheritance. It is occasionally useful to be able to implement an interface deep in the inheritance hierarchy. A good example is Comparable, where for example Integer is Comparable but its parent class Number is not. However, there aren't many other good examples of this in the core Java classes. It is usually bad practice to implement some random interface in a class whose primary purpose is something else. Implementing the interface in a private inner class is usually cleaner, and then of course it could just as well be an abstract class.

  • Dynamic proxies. The invaluable java.lang.reflect.Proxy class allows you to make an implementation of any interface at runtime, where calling any of the interface's methods results in a call to a single invoke method. There's no way to construct a dynamic proxy for an abstract class, so if you think it will be useful for users to make dynamic proxies that is one reason to favour an interface. (cglib can sometimes be used to achieve the same effect for abstract classes, but with several limitations, plus the documentation is really poor.)

Be careful with packages

The Java language has fairly limited ways of controlling the visibility of classes and methods. In particular, if a class or method is visible outside its package, then it is visible to all code in all packages. This means that if you define your API in several packages, you have to be careful to avoid being forced to make things public just so that code in other packages in the API can access them.

The simplest solution to avoid this is to put your whole API in one package. For an API with fewer than about 30 public classes this is usually the best approach.

If your API is too big for a single package to be appropriate, then you should plan to have private implementation packages. That is, some packages in your implementation are excluded from the Javadoc output and are not part of the public API, even though their contents are accessible. If you look at the JDK, for example, there are many sun.* and com.sun.* packages of this sort. Users who rely on the Javadoc output will not know of their existence. Users who browse the source code can see them, and can access the public classes and methods, but they are discouraged from doing so and warned that there is no guarantee that these classes will remain unchanged across revisions.

A good convention for private packages is to put internal in the name. So the Banana API might have public packages com.example.banana and com.example.banana.peel plus private packages com.example.banana.internal and com.example.banana.internal.peel.

Don't forget that the private packages are accessible. There may be security implications if arbitrary code can access these internals. Various techniques exist to address these. The NetBeans API tutorial describes one. In the JMX API, we use another. There is a class javax.management.JMX which contains only static methods and has no public constructor. This means that user code can never have an instance of this class. So in the private com.sun.jmx packages, we sometimes add a parameter of type JMX to sensitive public methods. If a caller can supply a non-null instance of this class, it must be coming from the javax.management package.

Other random tips

Here are some other random tips based on our experience with the JMX API and on the sources I mentioned.

Immutable classes are good. If a class can be immutable, then it should be. Rather than spelling out the reasons, I'll refer you to Item 13 in Effective Java. You wouldn't think of designing an API without having this book, right?

The only visible fields should be static and final. Again this one is pretty banal and I mention it only because certain early APIs in the core platform violated it. Not an example to follow.

Avoid eccentricity. There are many well-established conventions for Java code, with regard to identifier case, getters and setters, standard exception classes, and so on. Even if you think these conventions could have been better, don't replace them in your API. By doing so you force users to throw away what they already know and learn a new way of doing an old thing.

For instance, don't follow the bad example of java.nio and java.lang.ProcessBuilder where the time-honoured T getThing() and void setThing(T) methods are replaced by T thing() and ThisClass thing(T). Some people think this is neato-keen and others that it is an abomination, but either way it's not a well-known idiom so don't force your users to learn it.

Don't implement Cloneable. It is usually less useful than you might think to create a copy of an object. If you do need this functionality, rather than having a clone() method it's generally a better idea to define a "copy constructor" or static factory method. So for example class Banana might have a constructor or factory method like this:

      public Banana(Banana b) {      // copy constructor
    	  this(b.colour, b.length);
      }
      // ...or...
      public static Banana newInstance(Banana b) {
    	  return new Banana(b.colour, b.length);
      }
    

The advantage of the constructor is that it can be called from a subclass's constructor. The advantage of the static method is that it can return an instance of a subclass or an already-existent instance.

Item 10 of Effective Java covers clone() in excruciating detail.

Exceptions should usually be unchecked. Item 41 of Effective Java gives an excellent summary here. Use a checked exception "if the exceptional condition cannot be prevented by proper use of the API and the programmer using the API can take some useful action once confronted with the exception." In practice this usually means that a checked exception reflects a problem in interaction with the outside world, such as the network, filesystem, or windowing system. If the exception signals that parameters are incorrect or than an object is in the wrong state for the operation you're trying to do, then an unchecked exception (subclass of RuntimeException) is appropriate.

Design for inheritance or don't allow it. Item 15 of Effective Java tells you all you might want to know about this. The summary is that every method should be final by default (perhaps by virtue of being in a final class). Only if you can clearly document what happens if you override the method should it be possible to do so. And you should only do that if you have coded useful examples that do override the method.

Summary

  • Design to evolve.
  • Correctness, then simplicity, then efficiency.
  • Interfaces are overvalued.
  • Be careful with packages.
  • Read Effective Java.


Gregor Zeitlinger

Posts: 108
Nickname: gregor
Registered: Aug, 2005

Re: Java API Design Guidelines Posted: Dec 29, 2005 12:36 AM
Reply to this message Reply
Very good summary of API design.
Using these criteria I wonder if C# fits the bill better, as
1) C#s methods are final by default (not virtual)
2) C# has unchecked exceptions only (Spec# has checked exceptions again - but that's another story)

Also, I wonder if it's a good idea to make most methods final.
From my experience, it severly decreases the reusability of your code.
Woulnd't it be enough to lessen the guarantees that you provide for protected methods (i.e. that of internal packages - no guarantees).
Maybe I would even go as far as to say that every occurence of private should be replaced with proteced to aid code reusability.
One obvious drawback is that it is not clear which methods may safely be overridden and which may not be.

Gregor Zeitlinger

Posts: 108
Nickname: gregor
Registered: Aug, 2005

Re: Java API Design Guidelines Posted: Dec 29, 2005 1:23 AM
Reply to this message Reply
Why interfaces or abstract classes when you can have both?

For example, the Java collections framework has a List interface as well as an abstract base class AbstractList.
This design pattern (which I see very often) combines the best of both worlds:

The flexibility of interfaces
- multiple inheritance
- maximum implementation flexibility, if it is required
- a clean separation of the API and the implementation

The flexibility of classes
- utility methods and
- ability to evolve

This design pattern cannot always be used, OC.

Eamonn McManus

Posts: 20
Nickname: dichotomy
Registered: Apr, 2003

Re: Java API Design Guidelines Posted: Dec 29, 2005 1:55 AM
Reply to this message Reply
Gregor,

An interface and a skeleton abstract class that implements it is indeed what Bloch recommends in Effective Java, not surprisingly since he's the principal author of the Collections framework. This pattern is indeed occasionally useful but definitely not something you should use every time. Otherwise you just end up with twice as many types for little benefit.

Interface+skeleton doesn't really give the ability to evolve because you can only add methods to the skeleton, not the interface. This leads to a fairly messy situation where code that wants to call the extra methods has to use instanceof and a cast, and be prepared to deal with the case where the object is not in fact an instance of the skeleton class.

Concerning unchecked exceptions and C#, as you probably know there are those who feel that checked exceptions in Java are an unqualified mistake. I don't agree with this position: for things like IOException the fact that the compiler makes you handle it is a good thing. The real problem is that too many standard exceptions are checked when they shouldn't be.

Gregor Zeitlinger

Posts: 108
Nickname: gregor
Registered: Aug, 2005

Re: Java API Design Guidelines Posted: Dec 29, 2005 4:35 AM
Reply to this message Reply
> Interface+skeleton doesn't really give the ability to
> evolve because you can only add methods to the skeleton,
> not the interface.
Well, I suggest to change the API contract so that it is possible to add methods to the interface, as long as an implementation is added in the skeleton class. I have used this pattern and I think it works quite well.

It does mean, however, that you must be aware of the fact that you get a different contract, depending on whether or not you choose to extend the skeleton class. If you just implement the interface you can implement all methods any way you want to - but you have to be prepared to implement additional methods later on.

Robert Cooper

Posts: 7
Nickname: kebernet
Registered: Jun, 2005

Re: Java API Design Guidelines Posted: Dec 29, 2005 5:44 AM
Reply to this message Reply
> Why interfaces or abstract classes when you can
> have both?
>
> This design pattern cannot always be used, OC.

I tend to follow this logic myself most of the time. There are, however, some drawbacks:

If you have a complex API sometimes it is not clear exactly where to start with it. I know I want an IX but now I have to backtrack through AbstractX and down to find what my options are. (see Factory problem above)

You also run into the problem of people ignoring the AbstractX when they want to build a new IX implementation. Yes, this is a matter of education, however, the whole point of good API design is it should have a minimum of "Education" required. As a pattern, however, it is good.

You did, however, leave out one important element to Interfaces that I consider critical: DynamicProxies. Personally, I really think the JRE should support using an Object's signature as an Interface for a dynamic proxy. However, until that day comes, if you want maximum end-user functionality on your API, you have to have have the IX there to allow people to use DPs.

Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

No! Posted: Dec 29, 2005 6:40 AM
Reply to this message Reply
I'm sorry, but these guidelines are poor. They perpetuate a problem.

I don't fault the blogger, the fact is, the problem is pandemic. By all appearances, API designers just don't think about what developers have to do to unit test code that uses their APIs. It isn't on their radar yet.

Here are a couple of issues.

1) If you don't provide an interface, you are forcing a user to use your class, always.. even if it does something inappropriate under test.

2) When you make classes or methods final, you do the same thing.. you shortchange flexibility and make unit testing nearly impossible. Often, the only choice that you have is adaptation.

3) Static methods have the same issues. There isn't an easy way to mock them.

I find most sets of API design guidelines patronizing. They try to make misuse impossible, but they also make intelligent use less possible.

API designers have to start designing as if testing mattered.

I guess you can tell that I'm angry. Please don't take it personally, Eamonn. I respect you, and I know that everyone putting out API design guidelines is trying to do the right thing, but this is a significant problem.

James Watson

Posts: 2024
Nickname: watson
Registered: Sep, 2005

Re: No! Posted: Dec 29, 2005 8:11 AM
Reply to this message Reply
> I'm sorry, but these guidelines are poor. They perpetuate
> a problem.
>
> I don't fault the blogger, the fact is, the problem is
> pandemic. By all appearances, API designers just don't
> think about what developers have to do to unit test code
> that uses their APIs. It isn't on their radar yet.
>
> Here are a couple of issues.
>
> 1) If you don't provide an interface, you are forcing a
> user to use your class, always.. even if it does something
> inappropriate under test.

It's hard to imagine that anyone can test in Java at all condsidering commonly used classes like String are not interfaces and are final.

> I find most sets of API design guidelines patronizing.
> They try to make misuse impossible, but they also make
> intelligent use less possible.

I think you are taking a rather simplistic stance. The guide here makes some pretty good distinctions about when to use interfaces and when no to use them. Testing is important but I have to question what the developer is testing if they need to replace the entire API with mocked-up versions.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: No! Posted: Dec 29, 2005 8:21 AM
Reply to this message Reply
> I'm sorry, but these guidelines are poor. They perpetuate
> a problem.

However to be fair they do address problems that need attention. There is a strong basis in good software engineering principles in the motivations for the guidelines. Personally I like the motivations for the guidelines, but I feel the same way about theses guidelines, as I do about any set of guidelines: it is not a substitute for intelligent design and experience. My personal experience is that understanding the guidelines, and what problems they address is what counts.

One of the issues that Eamonn brings up and which I am currently studying is the fact that interfaces are under-specified:

Suppose String were an interface. Then you could never be sure that a String you got from somewhere obeyed the semantics you expect: it is immutable; its hashCode() is computed in a certain way; its length is never negative; and so on.

These are examples of the fact that interfaces don't contain contractual clauses (preconditions, postconditions, and class invariants). One solution is to make the contractual clauses part of the interface, in order to solve the under-specification problem.

In Heron there are two approaches which I am considering:
1) An interface would come with the contract embedded in it:


interface String {
signature {
int size() {
post { result >= 0; }
}
String concat(String s) {
post { result.size() == size() + s.size(); }
}
...
}
}


2. The other option is to separate the contract into a separate class.

My predicament is that the first approach is going to catch more errors, and prevent erroneous interface implementation. The second solution however is more flexible, and easier to parse and read.

So my question is: do you agree this solves the problem of interfaces being under-specified, and which approach do you prefer?

Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

Re: No! Posted: Dec 29, 2005 8:31 AM
Reply to this message Reply
> > I'm sorry, but these guidelines are poor. They
> perpetuate
> > a problem.
> >
> > I don't fault the blogger, the fact is, the problem is
> > pandemic. By all appearances, API designers just don't
> > think about what developers have to do to unit test
> code
> > that uses their APIs. It isn't on their radar yet.
> >
> > Here are a couple of issues.
> >
> > 1) If you don't provide an interface, you are forcing a
> > user to use your class, always.. even if it does
> something
> > inappropriate under test.
>
> It's hard to imagine that anyone can test in Java at all
> condsidering commonly used classes like String are not
> interfaces and are final.

I hear you, but I think you are offering a strawman. I haven't found anything in a string class that I would want to mock out during a test, but I do often find significant things that I can't mock out in API's I've used.

> > I find most sets of API design guidelines patronizing.
> > They try to make misuse impossible, but they also make
> > intelligent use less possible.
>
> I think you are taking a rather simplistic stance. The
> guide here makes some pretty good distinctions about when
> to use interfaces and when no to use them. Testing is
> important but I have to question what the developer is
> testing if they need to replace the entire API with
> mocked-up versions.

Again, I think that is a strawman. The guidelines did not even mention testing. The fact is, developers often do have to wrap whatever portion of an API they are using if the API designer decided to make methods and classes final.

James Watson

Posts: 2024
Nickname: watson
Registered: Sep, 2005

Re: No! Posted: Dec 29, 2005 8:57 AM
Reply to this message Reply
> > > I'm sorry, but these guidelines are poor. They
> > perpetuate
> > > a problem.
> > >
> > > I don't fault the blogger, the fact is, the problem
> is
> > > pandemic. By all appearances, API designers just
> don't
> > > think about what developers have to do to unit test
> > code
> > > that uses their APIs. It isn't on their radar yet.
> > >
> > > Here are a couple of issues.
> > >
> > > 1) If you don't provide an interface, you are forcing
> a
> > > user to use your class, always.. even if it does
> > something
> > > inappropriate under test.
> >
> > It's hard to imagine that anyone can test in Java at
> all
> > condsidering commonly used classes like String are not
> > interfaces and are final.
>
> I hear you, but I think you are offering a strawman.

No. THhe term strawman does fit. I haven't set up a weaker form of your argument. I haven't set up any argument at all. I have offered a counter-example.

> I haven't found anything in a string class that I would want
> to mock out during a test, but I do often find significant
> things that I can't mock out in API's I've used.

Let's be clear, String is an API that you have used. So clearly you agree that there is at least one API that doesn't need to be expressed as an interface.

> > > I find most sets of API design guidelines
> patronizing.
> > > They try to make misuse impossible, but they also
> make
> > > intelligent use less possible.
> >
> > I think you are taking a rather simplistic stance. The
> > guide here makes some pretty good distinctions about
> when
> > to use interfaces and when no to use them. Testing is
> > important but I have to question what the developer is
> > testing if they need to replace the entire API with
> > mocked-up versions.
>
> Again, I think that is a strawman.

If this is a strawman, then your orginal is also. The API guide states that one should not always use interfaces. Your response is that this causes problems with testing. If you do not believe that everything in the API should be expressed as an interface, you are making a straw man argument against the guidline as it doesn't say to never use interfaces.

> The guidelines did not
> even mention testing.

No but I think that the guidelines, if understood and followed properly should lead developers to something at least close to what you are desiring. But I will agree that testing should be explicitly mentioned.

> The fact is, developers often do
> have to wrap whatever portion of an API they are using if
> the API designer decided to make methods and classes final.

The guideline doesn't say to do this indiscriminately. I know that from my own API designing experience trying to express certain classes as interfaces is problemetic. For example, what is the point of defining an abstract factory as an interface? I've seen this done. One interface-one implementation is not a design strategy. Sometimes certain code is required to be executed in a certain order or at a certain time in order for anything to work properly. You could write a long comment about this hope implementors read and follow this advice and in the process lock yourself into this implementation detail which never should have been part of the public API in the first place. Or you could define an abstract class and make the method final and provide extension points for implementors for what they can change safely.

James Watson

Posts: 2024
Nickname: watson
Registered: Sep, 2005

Re: No! Posted: Dec 29, 2005 8:58 AM
Reply to this message Reply
> No. THhe term strawman does fit. I haven't set up a
> weaker form of your argument. I haven't set up any
> argument at all. I have offered a counter-example.

Sorry, should be "The term strawman does not fit"

Jaroslav Tulach

Posts: 2
Nickname: jtulach
Registered: Dec, 2005

Design with Testing in Mind! was: No! Posted: Dec 29, 2005 9:53 AM
Reply to this message Reply
> I don't fault the blogger, the fact is, the problem is
> pandemic. By all appearances, API designers just don't
> think about what developers have to do to unit test code
> that uses their APIs. It isn't on their radar yet.

Hey, this is an excellent observation! Yes, many APIs are not designed with testability in mind and that is a shame.

This might be caused by the fact that XP and especially test oriented development is still not fully rooted in minds of all developers or at least not in minds that design an API.

> I'm sorry, but these guidelines are poor.

I have to disagree (probably because my article and presentation is mentioned as a reference). They do not address the testing problem and it is a shame, but that has nothing to do with the guidelines being poor.

> API designers have to start designing as if testing
> mattered.

I've written another paper on automated testing (see http://openide.netbeans.org/tutorial/test-patterns.html) and one thing I am stressing there is that code must be written with testing mind.

The same implies to APIs. We need the API developers to provide automated testing support with the API (for example by also writing mock objects). I'll update our design guidelines (see http://openide.netbeans.org/tutorial/api-design.html) to talk about testing as well.

However I have to say "No!" to your desire to use (java) interfaces everywhere. As explained in previous texts interfaces in client APIs (like String) hurt evolution and that is why using them blindly will not automatically lead to long term benefits.

Testability is necessary, giving evolution a chance as well.

The clear requirement is to be able to use any API in automated tests, so let's remind ourselves with that and let us not try to sneak in simple, yet problematic, solutions like using interfaces everywhere. There has to be a solution to both of these problems, so let us find it, and please let's not start another interfaces vs. classes flamewar.

Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

Re: Design with Testing in Mind! was: No! Posted: Dec 29, 2005 10:03 AM
Reply to this message Reply
> This might be caused by the fact that XP and especially
> test oriented development is still not fully rooted in
> minds of all developers or at least not in minds that
> design an API.
>
> > I'm sorry, but these guidelines are poor.
>
> I have to disagree (probably because my article and
> presentation is mentioned as a reference). They do not
> address the testing problem and it is a shame, but that
> has nothing to do with the guidelines being poor.

Jaroslav, thanks. I did overreact. And I don't think that interfaces should be everywhere, but I do think that one of the best things that an API designer can do is write some testable code that uses their API. There are so many cases where it's obvious that it hasn't been done.

Gregor Zeitlinger

Posts: 108
Nickname: gregor
Registered: Aug, 2005

Re: No! Posted: Dec 29, 2005 11:52 AM
Reply to this message Reply
> One of the issues that Eamonn brings up and which I am
> currently studying is the fact that interfaces are
> under-specified:
have you checked out http://research.microsoft.com/specsharp/ and
http://www.jot.fm/issues/issue_2004_06/article2/article2.pdf
?
Spec# allows you to specify contracts (pre/postconditions, invariants) that can even be checked in the presence of subclasses.
One idea I really like is to use those contracts to verify the code - maybe these contracts can even make unit tests obsolete.

Flat View: This topic has 34 replies on 3 pages [ 1  2  3 | » ]
Topic: Mixins2 Previous Topic   Next Topic Topic: Mixins: Something Else You Can't Do With Java Generics?

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use