Sponsored Link •
Elliotte Rusty Harold talks with Bill Venners about the API design principles that guided the design of the XOM (XML Object Model) API, including enforcement of invariants, information hiding for simplicity, and not using assertions for air bags.
Elliotte Rusty Harold is a prolific author of numerous books about Java and XML, and creator of the popular Java website Cafe au Lait and XML website Cafe con Leche. He contributed to the development of JDOM, a popular XML processing API for Java. His most recent book, Processing XML with Java, shows how to parse, manipulate, and generate XML from Java applications using several XML APIs, including SAX, DOM, and JDOM.
At a meeting of the New York XML SIG in September, 2002, Harold unveiled an XML processing API of his own design: the XOM (XML Object Model) API. On Cafe au Lait and Cafe con Leche, Harold described XOM like this:
Like DOM, JDOM, dom4j, and ElectricXML, XOM is a read/write API that represents XML documents as trees of nodes. Where XOM diverges from these models is that it strives for absolute correctness and maximum simplicity. XOM is based on more than two years' experience with JDOM development, as well as the last year's effort writing Processing XML with Java. While documenting the various APIs I found lots of things to like and not like about all the APIs, and XOM is my effort to synthesize the best features of the existing APIs while eliminating the worst.
In this interview, which is being published in multiple installments, Elliotte Rusty Harold discusses the strengths and weaknesses of the various XML processing APIs for Java, the design problems with existing APIs, and the design philosophy behind XOM.
Bill Venners: You listed several API design principles in your talk at the New York XML SIG. I'd like to walk through them and get your comments. You said, "It's the class's responsibility to enforce its class invariants."
Elliotte Rusty Harold: XML has rules about names, about
where certain things can appear, about how constructs are
structured. In an XML API, classes should make sure that XML's
rules cannot be violated. If an
Element class, for
example, has a requirement that the element's name not contain
the asterisk character, the class should not rely on the client
programmer to feed in only names without asterisks. If the class
sees the client programmer trying to do that, the class should
throw an exception. I think that is a general design principle,
but it is one that is not enforced in a lot of XML APIs. They
rely on the client programmer knowing what is and is not legal
XML, and only giving their classes legal XML.
Bill Venners: Another design principle you listed in your talks was, "Verify pre-conditions."
Elliotte Rusty Harold: Suppose that a
String passed to a method you are writing must not
null, that the method fails if it is
null. You should check to see if that
null. If turns out to be
null, even though it was required to be
null, you should immediately throw an exception
before doing anything else, before potentially corrupting the
Bill Venners: Bad data shouldn't make an object unusable.
Elliotte Rusty Harold: Any data that is so bad as to make an object unusable when passed to a method should not be accepted by that method.
Bill Venners: And after the exception is thrown, the object should be in the same state as it was before?
Elliotte Rusty Harold: Right. The object should not have changed. The badness of the passed-in data should be detected at the first opportunity.
Bill Venners: Another design principle you listed was, "Hide as much of the implementation as possible." What is that about?
Elliotte Rusty Harold: That's just a good general principle of object-oriented programming. You want to expose a certain API and keep everything else private. You don't want your clients to see how you are doing certain things that they don't need to know.
If you expose too much, there are two costs. One is, it confuses the client programmers. It takes mental effort to learn and understand these things, and hey, they don't need to know. Smaller is better. Smaller is simpler. That's one of the big advantages of data encapsulation that's not often pushed in object-oriented textbooks, though I think it's one of the most important in practice.
The other advantage to implementation hiding is very well
known: it allows you to vary the implementation. If later you
java.lang.String class isn't fast enough
for your needs, you can change the implementation to use arrays
chars. You can get away with that kind of change
without breaking client code.
Hiding implementation also means that clients can't sneak behind the public methods and break things by calling methods out of order than the implementation would normally call them, for example. But far and away the most important practical reason for encapsulation is it makes the classes simpler and easier to understand.
Bill Venners: You also mentioned in your talk, "Design for subclassing or prohibit it."
Elliotte Rusty Harold: That is taken directly from Joshua Bloch's book Effective Java. If you're going to allow subclasses, you must remember that subclasses typically need to have a greater access to the superclass members than the general public. Subclasses also need to access the superclass in different ways from the general public. So designing for subclassing takes extra effort. You really need to think about what a subclass is going to do, what it can do, what it should be able to do. It is much harder to get the design right for a subclass than just for a client object that's merely invoking the methods.
That being said, sometimes subclasses are in fact useful, so you do the work. If you don't do the work, if you don't really think about what subclasses do and don't need and how they'll interact with the superclass, you should make the class final to prevent it from being subclassed. Because often, subclasses sneak behind the normal barriers of access protection, and they can get at pieces of the class in ways you had not intended.
So one principle I follow in design is that all classes start their life as final. If at some point I discover a reasonable need to subclass, either my own or somebody else's, then I will remove the final modifier. I take this approach because once a class is published as non-final, it becomes very hard to make the class final later on.
Bill Venners: What XML principles did you adhere to in your XOM API design? You listed a few in your talk, for example, "All objects can be written as well-formed XML text." All objects?
Elliotte Rusty Harold: All
all objects that represent part of an XML document, so the
Element object, the
Text object, etc.—can be written as well-formed
Bill Venners: Is that not true of JDOM?
Elliotte Rusty Harold: No, it's not true of JDOM. It's not true of DOM. It's not true of most other XML processing APIs.
Bill Venners: How is it not true of JDOM?
Elliotte Rusty Harold: As I said earlier, you can use
Strings in JDOM that contain control characters that
cannot be serialized. JDOM doesn't make the checks it needs to
guarantee well-formedness in several areas.
Bill Venners: I see. So because the API doesn't ensure
well-formedness when data is passed to the
objects, well-formed XML is not guaranteed to come out when those
objects are serialized. In your talk you also said, "Validity can
be enforced by subclasses." What did you mean by that?
Elliotte Rusty Harold: Going back to the principle,
"Design for subclassing or prohibit it," in most of the XOM
classes, I designed for subclassing. Let's say you're developing
an XHTML package, a set of subclasses of the standard XOM
classes. You have classes for each of the specific element types
in XHTML, such as
BodyElement, all of
Element. Each of these classes could
add additional constraints to those usually enforced by XOM. So
BodyElement subclass could require that the
element name be
"body" and the namespace URI be the
Or, in an RSS package with a
that extends XOM's
Element class, you could verify
that the text of every
LinkElement is actually a
URI. None of these additional checks are required by XML, but
specific XML applications might have such further requirements.
You can enforce those in subclasses. On the other hand, you can't
remove checks. A subclass is not allowed to decide, for example,
that it going to allow white space in
Designing the XOM classes for subclassing took a lot of effort. It would have been much easier in a language like Eiffel, that has real assertions that are inherited by subclasses.
Bill Venners: You said in your talk, "Classes do not
Serializable, use XML." That would
probably be the main question I would have in a XOM design
review. By simply marking the classes
you give clients a choice to serialize via XML or Java object
serialization without adding much clutter to the public API. Why
did you choose not to?
Elliotte Rusty Harold: XML is a good serialization
format. It's often smaller, more compact, and faster than Java's
binary object serialization. If you have a
Element object, and you want to blast it
across the network to somebody else, XML is much more portable.
It's much more efficient to send it as XML, as text on the wire,
than it is to serialize this object into Java binary
serialization format. The only case where you might perhaps want
to use object serialization is if you're doing remote method
invocation. But my response to that is, well this is XML. We
probably ought to be doing SOAP or XML-RPC, or some REST-ful
Bill Venners: Another comment you made in your talk was, "Lack of generics really hurts the Collections API, hence don't use it." Explain your reasoning.
Elliotte Rusty Harold: Essentially, there's no way
currently in Java to say, "This is not just a generic list. This
is a list of
Attributes" Anytime you put an object into a
java.util.List, you lose some type information. That
results in a lot of casting, a lot of
checks, and it's just plain ugly. There's probably a little
performance cost, but I don't care about that. I do care that
It's not that hard to implement your own lists, something we
all learned about in Data Structures 201. It was still too hard
for me to do, though. So internally in XOM, I used a
java.util.List. I used the facade design pattern to
provide type safe list operations in the public API. All the
instanceof checks and everything else
that's necessary with
java.util.Lists are done in
the private parts of the classes.
Interestingly, this is the exact reverse of how JDOM does it.
In its private parts, JDOM uses its own
class that was written by the JDOM developers.
FilterList is a very sophisticated list with a lot
of power. It knows a lot of details about the specific JDOM
objects. In the API JDOM exposes to the world, however, it might
as well be any other
java.util.List that contains
objects. None of that power, knowledge, or sophistication is
seen. Behind the scenes in XOM, I'm just using the standard
java.util.List, but out front it looks a lot
Bill Venners: I think the tradeoff there is that one of the advantages of using the Java Collections API in your public interface is that everyone already knows what they are.
Elliotte Rusty Harold: Right, that's certainly an
advantage, but I don't think the XOM lists are so challenging
that anybody is going to have excessive trouble learning them.
NodeList interface, for example, has two
methods, one to return the size and another to get the item at a
particular index. It's a read-only list, not a read-write list.
If you want to write
Elements into the
List, you use the
appendChild methods. You
can't change the lists that are exposed to you.
Bill Venners: You also mentioned in your talk that, "Assertions that can be turned off are pointless." Why is that?
Elliotte Rusty Harold: Imagine you're designing a car for General Motors. You put in the most wonderful air bags that have ever been seen. You've got airbags on the side, airbags in front, airbags in back. These airbags don't hurt small babies. They don't break anybody's neck. They only go off when they're supposed to, not when somebody's walking through the parking lot kicking all the cars' bumpers. You test the car, you run it into walls. You put crash test dummies in it. You film it. You drive the car around on test tracks. You drive it up and down mountains. You make nice TV commercials. These are great airbags that make riding in the car much safer. But just as you're getting ready to go into production to send the car out to consumers, you take out all the airbags. That's what Java 1.4 assertions are like.
Java's assertions are only intended for testing. They aren't intended as part of the real class's design. There's a runtime flag that says, ignore all the assertions. Does that make all the problems go away? No. If buggy data is passed in, it is still buggy, and it will still cause problems, but now the problems will be hidden. It is much better to find out about them sooner rather than later.
Bill Venners: I asked both Josh Bloch and James Gosling about the appropriate use for assertions. They helped me understand that I should continue to use exceptions where I used them before assertions came along. I shouldn't just start using assertions in places where I traditionally used exceptions, such as checking for preconditions at the beginning of a method. So I would say that explicit checks for preconditions that result in explicit thrown exceptions are like airbags in cars. You don't take those away at runtime, so you don't implement them with assertions.
Where I find that assertions make sense is when I have something that's kind of complex and I lack confidence. For example, perhaps at one point in a particular method I'm assuming something is true that really needs to be set up by other methods. And I think it's going to work, but I don't feel confident that even if it works now, it will continue to work as the class is changed over time. I would put an assertion in there for two reasons. One is that if the assertion did fail in testing an error message would pop up. It's almost like a little unit test that runs as the application is running. It is not something that is enforcing client constraints such as method preconditions. The other reason I think it's helpful to put an assertion in there is that the assertion actually communicates some intent to other programmers. Without the assertion statement, it might not be immediately obvious to other programmers that as I was programming along here, I was making the assumption that the asserted condition is true.
Elliotte Rusty Harold: That sounds reasonable. It's
certainly not how I've heard people pushing assertions over the
last year since Java 1.4 came out. If you look at the articles
written about assertions at Sun's Java web site, JavaWorld, and
elsewhere, what you see is people using them most commonly as a
IllegalArgumentException and similar
things. And that to me just seems insane.
Come back Monday, August 25 for the next installment of this conversation with Elliotte Rusty Harold. If you'd like to receive a brief weekly email announcing new articles at Artima.com, please subscribe to the Artima Newsletter.
Harold is author of Processing XML with Java: A Guide to SAX,
DOM, JDOM, JAXP, and TrAX, which is available on Amazon.com
XOM, Elliotte Rusty Harold's XML Object Model API:
Cafe au Lait: Elliotte Rusty Harold's site of Java News and
Cafe con Leche: Elliotte Rusty Harold's site of XML News and
SAX, the Simple API for XML Processing:
DOM, the W3C's Document Object Model API:
Common API for XML Pull Parsing:
Xerces Native Interface (XNI):
TrAX (Tranformation API for XML):
Jaxen (a Java XPath engine):