How To Design A Good API and Why it Matters

Uploaded by GoogleTechTalks on 08.10.2007

>> First, I would like to thank you and welcome you all to the latest in our series of talks
on advance topics and programming languages. The purpose of these series of talks is to
expose all of the great domain knowledge in programming languages that we have at Google.
So that--what that means is that everybody in this audience, all of you geniuses who
work for Google and myself discounted, should give a talk. So please, come to me, my name
Jeremy Manson email me, IM me whatever and tell me what talk you are going to give and
I will set it up and give you a talk. You don't have to be an author like Josh or some
of the people we have coming up in order to do so. Josh--and we're running late. Josh
didn't want me to build him up too much and give him a swell head. So, I want to introduce
him. I'll just say that in a company full of geniuses, it's his star that shines probably
the most brightly. And I just want to... >> BLOCH: I'll get you for that.
>> I just want to introduce this man whose boot heel I am not worthy to lick, Joshua
Bloch ladies and gentlemen. >> BLOCH: Normally at this point, I thank
my introducer for the introduction but today I think I'll dispense with that formality.
So, I should--I should also say by the way that this is unfortunately rather a long talk.
I try to have short talks with one or two key ideas but this subject matter just doesn't
lend itself to that. So, please hold your questions till the end and in fact, we'll
probably use up the hour without questions but then I'll hang around for as long as you
want and answer all the questions that you have. So, why is API design important? Well,
APIs can be among a company's greatest assets. A good API is something that people invest
heavily in. They do this in obvious ways and in less obvious ways. Obvious ways are may
be a by product built around the APIs. They write to the API. But the less obvious ways
is they learn it. They spend hours and hours--actually, they spend months learning the APIs. And once
they've have done that, you know, they don't want to learn a new one because they have
to unlearn everything they know and replace it with something else. And furthermore, the
API is just wired throughout the infrastructure at a company. So, a successful API can make
a company, can give a franchise that last. So--I guess it's about twenty-five years now
and similarly a bad API can be among a company's greatest liabilities. And there are a couple
of reasons for this. First of all, a bad API can cause an unending stream of, you know,
support phone calls because people cannot make the thing do what it ought to do and
it can inhibit a company's ability to move forward because once you have a bad API you
cannot change it at will. You're pretty much stuck with it forever. You have one chance
to get it right. So, that's pretty scary and with that in mind, you want to learn how to
make APIs that will stand the test of time. So, now you know why API design is important
but why is it important to you? Not all of you may think of yourselves as API designers.
Well, it turns out that all of you are API designers. Anyone who programs a computer
is an API designer. And the reason is that good programming is inherently modular and
these inter-modular boundaries are themselves APIs. Furthermore, good APIs tend to get reused.
If you've written a module and it's good at doing something, you now, one of these days
one of your co-worker is going to need to do the same thing. And gosh, you know you've
already got this module that does it but once he's using that API, you are no longer free
to change it at will because if you change it you'll break his program. And if he has
ten friends and they all start using it then you're really hosed. Finally, thinking in
terms of API design tends to improve the quality of the programs that you write. It tends to
sort of keep you from just hacking things together. It tends to make you want to write
nice units, you now, that are--that are composable, that are reusable and that are sensible. Now,
one other question we've got to get out of the way at the beginning is, why am I talking
about API at what is billed as a language design series? And the glib answer of course
is that Jeremy asked me to do it. And Jeremy used to be friend, so, of course, I said,
"Yeah, of course I'll do it." But in fact the real answer is that API design and language
design are very, very similar. The only real difference is that API design is constrained
by the syntactic--sorry, the syntax of the language for which you're writing the API.
Whereas, when you're designing a language you have the flexibility to do anything you
like with the syntax. But in fact, whether you're designing a language or an API, you
are creating a tool for programmers to express their intent to the machine and to other programmers,
who read the program, maintain it, modify it and so forth. Finally, these days you don't
really think in terms of a language or a library alone. A language and a library together comprise
a platform and when you're designing a language, you design the core libraries hand in hand
with the language. So, really the skill set for designing good APIs and for designing
languages is pretty much the same. So, what are the characteristics of a good API? First
of all, it's easy to learn and it's easy to use, even without documentation. So, a good
API should be easy to memorize. It should just plain make sense. And the flip side of
that is, not only should it be easy to use a good API but it should hard to misuse a
good API. It should be hard or impossible to use--misuse a good API. That is basically,
a good API should simply force you to do the right thing. It should be easy to read and
to maintain code written to that API. The API should be sufficiently powerful to do
what it has to do. Note that I didn't say the API should be powerful. It is not the
case that the more powerful an API is, the better it is. It should basically be just
powerful enough to do what it needs to do. But it should be easy to evolve the API over
time because there will be new needs later on. So, what you want to do is want to write
an API that meets its requirements and that can evolve to meet future requirements. And
finally, the API has to be appropriate to the audience. What is a good API for let's
say a Wall Street analyst is probably not a good API for a physicist because they have
different terminology. They think differently. So their API has to be aimed at its audience.
So now, we know what the characteristics are. How do we achieve them? And that's what's
the rest of this talk is about. The talk is divided into five sections. The first one
is on the process of the API design. I'm not a big process weenie but I found over the
years that there are certain things that all good API designs have in common in terms of
the process used to create them. So, I'll try to get over that. Then, the general principles
of API design, then those principles as they apply to classes, as they apply to methods,
and as they apply to exceptions. And finally, if I have time I'll show a couple of refactorings
where we improve API designs. So, what is the process of API design like? Well, the
first thing you got to do is you got to gather the requirements but do it with a healthy
dose of speck--of skepticism. Because often when you ask people, you know, "Well, what
does this API have to do for you?" What you'll get won't be a real set of requirements, it'll
be a set of proposed solutions and a better solution may exist. So, you know, if someone
tells you, let's say, "We need to precisely control the garbage collection intervals and
the maximum time that each garbage collection can take." You know, that's not really a requirement.
I mean, the requirement is, you know, we need to be able to run a server smoothly while
any garbage collection takes place. How you choose to achieve that is up to you. Your
job is to extract the requirements from the stakeholders in your API and often it's real
give and take process. Once you've got the requirements, they should take the form of
used cases, and by used cases, I simply mean, the problems that your API should be able
to solve. And these are extremely important because they provide the benchmark against
which you can measure any proposed solution. One thing you should keep in mind is that
it can be easier and more rewarding to build a more general solution than what you've been
asked to do. This doesn't mean you should just say, "Oh, I'm going to build a framework,"
every time someone asks you to do something. But sometimes the more specific thing is more
difficult to build as well as being less powerful. So, always keep an open when you're--when
you're looking at those initial requirements. Oh, let's see, I guess I have another example
here of what they say and what they mean. When they say, "We need new data structures
and RPCs with the Version 2 attributes," this actually happened to me at a company called
Transarc, you know, when we were kind of upgrading. They said, you know, "Make a whole new set
of data structures and a whole new set of APIs." What they really meant was, "We need
a new data format that will accommodate all further evolution in the internal data structures."
You know, because you don't have to want to have to make a whole new set of data structures
and a whole new set of on the wire and on disk interfaces every time you decide to add
a few attributes to your server. So in fact, I made the system much more dynamic and we
never had to do that again. So, you should start with a really short spec, one page is
ideal. At this stage in an API design, agility definitely trumps completeness. The worst
thing you can do is to send six smart guys off into a room and have them sit there with
the door closed for six months and come out with a 240 specification document. And believe
me this has been done many, many times. It's an awful idea because at that point, first
of all, their ego is invested in what they've just done. They're going to build it even
if it's a piece of crap. And second of all, you know, how do you know if it's any good?
It's like this big, long, hairy spec. It's no longer agile. If they made a fundamental
mistake, then you've got to change all 240 pages of it. But it may fail to satisfy some
sort of key requirement that they didn't really understand before they started. So, what you
want to do in the beginning is have the entire API spec on one page. In this way, you can
bounce that spec off as many stakeholders as possible. Listen to what they have to say
and take it seriously. If they say, "No, I'm sorry, this won't do for me because I cannot
write such and such a program," think about it. You know, you may say, "Well, you shouldn't
be writing that kind of program. It's a really bad idea." But more likely, you may say, "Oh
gee, I didn't think of that. This really is important. What if we structure it this way?"
The whole thing is only a page long. You can do major refactorings in ten minutes. If you
keep the spec short, it's easy to modify. And you flesh that spec out only as you gain
confidence that you're on the right track. And this necessarily involves coding. In particular,
it involves coding to the API that you are defining. It doesn't involve implementing
the API. It involves pretending it's already been implemented. So, what does it look like
when you do it right? Well, here's an example that I was writing at about the time that
I was putting this talk together for OOPSLA some number of months ago. Someone wanted
the ability to retry a computation in the face of failure. And I said, "Oh well, you
know, we have this executor framework, otherwise known as the executor framework." And really,
all we want here is a retry policy that tells you how you might choose to retry the thing
in the face of failure. So, you know, here's a little interface and it's got a couple of
methods. One tells you, if a given failure is recoverable. You pass in the exception
and it just gives you true or false. We should try to recover from this one or we shouldn't.
And the second one computes the next delay in terms of, the initial start time and the
number of previous retries and by passing in all these data, the actual retry policy
can be stateless. So, you can have singletons. You can have a retry policy called exponential
backoff. But you're going to have to store any data in each exponential back-off instance.
It really is just a retry policy and it's called exponential backoff. And that's kind
of all there is to that one. And this isn't really very complete. This is not, you know,
a spec of a quality that I could use for Java doc. It's not a spec that someone could use
to implement to but it's a spec that's good enough for someone to look at it and say,
"Yes. It does do what I need or it doesn't." The rest of it is on another slide here. This
is a set of static utilities that lets you actually use retry policies. So, what can
you do? Well, the first thing you can is you can pass in an executor service and retry
policy and get a retrying executor service. It implements the same interface, which is
executor service. So, if you already know how to use an executor service, you know how
to use the retrying executor service and that's great by the way. That's a good way to keep
the sort of the conceptual weight of an API small. Use interfaces that have already been
designed--defined, in this case, executor service. And what else do we have? We have
another kind of a retrying executor service. What are--what are the difference between
the first two? I haven't looked at this for a while so, I apologize for that. Anyway,
it doesn't--it doesn't matter. And then, we have a couple of wrappers, one of which takes
a callable and returns a retrying callable and takes a runnable, returns to retrying
runnable. And then, we have a couple examples of the retry policies themselves. These are
static factories, we have, if you want an exponential backoff, you get one. And these
are the parameters that describe your exponential backoff. What's the initial delay? In what
unit, you know, 10 seconds, a hundred milliseconds, whatever and then, the timeout, which I'm
not sure what that means. So, by the way, this is actually interesting. This tells me
this wasn't quite good enough, you know. What I wanted to show you is that something that's
really simple. It fits on the slide or two, is enough to communicate your intent and enough
to sort of, figure out whether it's good enough and you know, try it out. And I think the
answer here is it's almost good enough but not quite good enough. I guess, the timeout
probably, is the overall timeout. Like after how long of trying and retrying do you finally
give up. But I think it should have said that somewhere on this. But anyway, you get the
idea. The idea is that this is a very small description of an API. But it's big enough
to find out if it's good enough to do what needs to be done. And if it's not, it's easy
to modify it. You should write to the API early and often. Of course, you should start
before you've implemented the API because this saves you from having to throw away an
implementation of a bad API. You know if you--if you first specify it, then implement it, then
try the implementation and decide that the API was garbage, well, you've wasted lots
of time implementing it. And as I said, you should start before you've even specified
it properly because that saves you from having to throw away detailed specifications for
broken APIs. You should continue writing to the API as you flesh it out and this is important.
Some people, sort of stop writing to the API about halfway through the process and just
go on this death march to implementation. The problem with that is you get nasty surprises
about a week before you ship when you try writing to it again and you find that, you
know, "Oh, my gosh. It actually doesn't solve this important case that we thought it solved."
And some people worry. They worry that, you know, that all these coding to the API is
a waste of time when they should be implementing it. But that nothing could be further from
the truth. Those initial pieces of code that you write to any API are among the most important
pieces of code that you'll ever write to it. The code lives on in the examples that you
publish for how to use that API. And those examples tend to get emulated heavily. If
you get them right, you seeded the market with good uses of your API. If you get them
wrong, conversely, you know, you've ensured that there will be broken programs floating
around the web for the next ten years. And I used to, you know believe this with all
my heart and soul. But now, I actually can point to a proof of it. It turns out in the
last OOPSLA, there was a paper published called Design Fragments by Fairbanks, Garlan and
Scherlis from CMU. And they actually traced mistakes in the original applets that were
shipped out with, you know, the first release of Java into broken concurrent programs, thousands
of them that still exist on the web today. So, you know, the way I put this is, example
program should be exemplary. There's a reason they call them example programs. And the programs
that--the first programs that you write to an API, as you are fleshing it out, invariably
become the example programs. You know, so, you know, my rule of thumb is you should spend
ten times as much time on every line of example code as you do production code. That may sound
backwards to you but I really believe it. Writing to an SPI, that is a Service Provider
Interface is even more important than writing to any other kind of API. You all probably
what SPIs are or maybe you don't. If you know an SPI is, raise your hand. Okay. So, an SPI
is a special kind of an API which you use to provide a new means of doing something.
It's not the API which the programmers write to rather, it's the plug-in API that, let's
say, lets--RSADSI Incorporated provides their encryption methods and Sun Microsystems provide
their encryption methods. So, the user of this encryption API can use a higher level
API, which then dispatches to these encryption methods. And the thing about SPIs is that
you're supposed to be able to hide very, very different implementations underneath them.
And if you, you know, write an SPI and you only have one provider it turns out that is
a practical matter, you will probably never be able to support another. Once you try to
do the second, you'll find that there's something about that SPI that ties it forever to the
only implementation you actually thought about. If on the other hand, you do two implementations
rather than one. Then, you'll probably be good enough. You'll probably be able to support
subsequent ones with some difficulty. But if you do three, it will probably work fine
for any number. If it works for three, the fourth won't all be that different from each
of the first three. I found this out myself and then, I saw it in a book, Will Tracz discovered
this and called it The Rule of Threes in a book, which was subtitled Confessions of a
Used Program Salesman because it was a book about a software reuse. And by the way, here
we explain the subtle coding that is used throughout this talk. Whenever you see something
green, it means this is good, do it this way. And when you see something in red, it means
this is bad, don't do it that way. You should maintain realistic expectations throughout
the process of API design. It turns out that most API designs are over constrained. People
want them to do more than they can possibly do. So, you are going to have to make compromises.
You cannot please everyone. If you try to please everyone, you come up with a pig. You
come with big, nasty APIs that no one will ever be able to use properly. So, what you
should do? And this may sound strange, is you should aim to displease everyone equally.
The idea is that, you know if one of your important stakeholders is very displeased
and the others are really pleased, that's probably a problem because your API isn't
doing something it has to do. If everyone is like less than a 100% happy but they're
all happy enough then, you've probably done the right thing. But do not misinterpret this
as saying I favor design by committee and you should take everyone's ideas and mush
them altogether. You do need one sort of strong design lead that can ensure that the API that
you're design is cohesive and pretty and clearly the work of, you know, one single mind or
at least a single minded body. And that's always a little bit of a trade off, being
able to satisfy the needs of many costumers and yet produce something that is, you know,
beautiful and cohesive. Expect to make mistakes. API design is hard. Now luckily, a few years
of real world use will always flush out the mistakes. Unfortunately, by that time it's
like too late to do anything about them. Although, you can write nice talks and tell people about
the mistakes so they don't make them again and it's kind of what I'm doing here today.
So, given that you're going to make mistakes and you're going to be stuck with the original
API, write the API so that at least you can sort of add to it and produce something that
will help you get around the shortcomings of your original designs. The recent example
of that in my life is in the collections API which I did around 1997, 1998. There were
some real flaws in the sorted sets and sort of map implementations. In particular, you
know, they're a little bit asymmetric. It's much easier to a forward than backward and
there are couple other things. I knew about these flaws at that time but I didn't know
how to fix them. However, we were able to extend that API. So, if you look at the most
recent release of Java 6. You'll see something called the Navigable Set and Navigable Map,
which extends sorted set and sorted map and provide, you know, additional methods that
basically fix those difficulties. You know and it's not a perfect solution because there
are some things that implement the old--the older interfaces and they're not fixed yet.
But at least all the standard collection implementations from Sun now are fixed. So, what are the general
principles of good API design? First all, an API should do one thing and do it well.
And I should say that almost all of what I'm going to say for the next five minutes may
just sound like motherhood and apple pie. But, there's more to it than that. I'm going
to try to give you actionable advice. I'm going to try to take the sort of the standard
old souls like in API should do one thing and do it well and see what it really means
and tell you how to achieve it. So, you know, in this case, the functionality should be
easy to explain. If it's not easy to explain, then it's not doing one thing and doing it
well. It's a mess. If you can't come up with a good name for it, then it's a mess. The
names are the API talking back to you, so listen to them. When you try to name those
methods and those classes, you know, if you come up with the really complicated name like
DynAnyFactoryOperations or underscore BindingIteratorImplBase switch, you know, actually violates the naming
conventions of platen--platform, sorry. ENCODING_CDR_ENCAPS, you know, clearly you've got problems. Any
API that contains this sort of stuff is a mess. Oh, what about this, OMGVMCID? I know
OMG is, "Oh, my god," but I can't--I can't figure out the rest of it. And by the way
least you think I'm just making this stuff up, all of these comes from an actual Java
platform API. And I won't tell what it is except to say that it's club Good API names
are like a font. Yeah, I know what a font is. Sure, you know, it's like, it's italic
or bold or whatever. You know, a set, I know what a set is. A private key, a lock, a ThreadFactory,
these things are, you know, they instantly communicate what they are. And the methods,
you know the classes all of them should be like that. Looking at them, you know, it should
be clear what they are. And good names drive good designs. You know, once you have something
that's called a set. You know what the operations are. You insert things from sets, you remove,
you test for containments. So, good names drive good designs and bad names are an indication
of bad designs. So, listen to those names speaking to you. And if you just you can't
get it to work out right, then you probably not trying to build something reasonable.
So, always remain amenable to splitting a module up if you're trying to cramp too much
into a module, or to putting multiple modules together, if you're trying to expose sort
of internal details that ought to be hidden. Maybe you should just make a bigger module
that hides some of those details. An API should be as small as possible but no smaller. This
principle is usually attributed to Einstein. Although I looked really, really hard and
I don't believe he ever said it. I believe that it's, you know--he probably believed
it but he didn't say it. At any rate the API should satisfy its requirements and if you
only remember one thing from the talk today, please remember this. When it doubt, leave
it out. That applies to everything. It applies to functionality, to classes, to methods,
to parameters within a method, anything. If you have any doubts about whether to include
something, leave it out. You'll probably be able to add it later, but you most certainly
will not be able to take it out once you put it into an API. Once you put it in people
will be using it, if you take it out they will scream bloody murder. So, if you ever
have any doubts about whether to include something, leave it out. And if that's all you take away
with you from today's talk, then, I think it's been worth your hour. Of course, that's
just my judgment. Anyway, the--when you're thinking about the size of an API, the conceptual
weight is more important than the bulk. By bulk, I mean the number of methods, the number
of class, the number parameters. What's really important is the number of concepts. When
I'm learning this API, how many different things do I have to learn about? And there
are a number of ways to decrease the conceptual weight of an API. The most important one is
reusing interfaces. So, for example, if you look at the collection's framework, there
are many implementations of the set interface, you know, whether it's TreeSet. The original
ones were, you know, a HashSet, TreeSet and then we add a LinkedHashSet and more recently
a whole slew of concurrent set implementations. You don't have to learn any new APIs. You
learn the Set API and we can add new functionality, you know, we can richness without making you
learn any new API. So, that's one of the great ways to increase the power to weight ratio.
That's the important thing. You want to be able to do a lot without learning a lot. The
implementation shouldn't impact the API. Once again this is motherhood but what does it
really mean? It means don't put any implementation details into the API. They confuse the users
and they inhibit their freedom to decide. They inhibit the implementer's freedom to
change the API later. So, you know, for example, let us say, if you have some API that's about
phone numbers but it throw a sequel exceptions and now you want to re-implement it on top
of some proprietary data store rather than a sequel data store. But your clients are
already trying to catch the sequel exceptions. You know, what do you do? Well, you can emulate
those olds sequel exceptions but that's crazy. So that means that you should make sure that
the exceptions that you throw are kind of at the same layer of abstraction as the rest
of the API is. That's just one example where implementation details kind of leak into APIs.
The important thing is you have to be aware of what actually is an implementation detail.
You don't want to over specify your methods. You don't want the specification of a method
to involve something that is in the implementation detail and that you would like to be able
to change later on. So, here's an example where we did that. Don't specify your hash
functions. You might think that, you know, exactly what value is returned by the hash
code method is a proper part of a spec but it isn't. It's an implementation detail. The
spec should simply say it returns an integer and, you know, with high probability the integer
will differ for two different objects and furthermore it should be cheap to calculate
the thing. But exactly what number is returned you should have the flexibility to change
that from release to release as you learn about flaws in your old hash functions and
as the technology improves and hash functions improve. Of course, you cannot do that if
you're writing a persistent data store. If those hash functions are going to be use to
store data on disk then they can't change. But that's a very special kind of hash function.
Those ones must be specified but the great majority of hash functions out there shouldn't
be. And we got this very wrong in initial releases of Java and unfortunately that tradition
has stuck to the point where almost all of these hash functions are specified. But they
really shouldn't be. Finally, you shouldn't let the implementation details kind of just
leak into the API. The example I gave you before with an exception is an example where,
you know, you didn't really think hard about them and say, "Oh yeah, you know, we should
maybe throw a sequel exception here." You probably just wrote it and you realized, "Gee,
I'm calling something that throws a sequel exception, so I have to propagate it out."
That's a case where an implementation detail is just sort of leaking. Another example,
a really notorious example in Java is, if you simply say, "Implements serializable."
Once you've done that, your entire implementation has just sort of leaked out as part of your
API because the serial form consists of the entire field that comprise your object even
your private fields, so all of the sudden the private fields are part of the public
API and that's really, really bad. And the way around that by the way is to design your
serial forms carefully. Don't just say implement serializable. You should minimize the accessibility
of everything. That means you should make your classes, your members, your fields, all,
as private as possible. One specific case is that public classes should have no public
fields with the exception of their constants, which aren't really fields. This maximizes
what they call information hiding. Parnas is the guy who came up with that term. And
it minimizes--minimizes the coupling between APIs. You know, if things are kind of hidden
behind inter-modular boundaries, they can be change freely. And this allows modules
to be understood, to be used, to be built, to be optimized, debugged, tested, and what
have you individually and in parallel. So you can have multiple teams, you know, dealing
with multiple APIs concurrently. If on the other hand the APIs sort of expose everything
and each, you know, module is sort of messing around with other modules, then there is very
little that you can do to any module without affecting a whole slew of modules around it.
Names matter a lot. There are some people who think that names don't matter and, you
know, when sit down and you say, "Well, this isn't named right." They say, "Don't waste
your time. Let's just move on. It's good enough." Names in an API that are going to be used
by anyone else and that includes yourself in a few months, matter an awful lot. The
idea is that every API is kind of a little language and people who are going to use your
API have to learn that language and then speak in that language and that means names should
be largely self explanatory. You should avoid cryptic abbreviations. So the original Unix
names I think, failed this one miserably. You should be consistent. It's very important
that same word means the same things when you used repeatedly in your API. And that
you don't have multiple words meaning that same thing. So let us say you have a remove
and a delete in the same API. That's almost always wrong. You know, what's the difference
between remove and delete? Well, I don't know. When I listen to those two things, they seem
to mean the same thing. If they do mean the same thing, then call them both the same thing.
If they don't, then make the names different enough to tell you how they differ. If they
were called, let's say delete and expunge, I would know that expunge was a more--a permanent
kind of removal or something like that. Not only should you strive for consistency but
you should strive for symmetry. So, if your API has, let's say two verbs, add and remove,
and two nouns, entry and key, I'd like to see, you know, add entry, add key, remove
entry and remove key. If one of them is missing, there should be a very good reason for it.
I'm not saying that all API should be symmetric but the great bulk of them should. And if
you get it right, the code will read like prose. That's the prize. So, you know, in
this case the code reads, "If the car's speed is more than twice a speed limit and the speaker
should generate in alert that says watch out for cops." That's pretty much English. It
reads like prose and that's an indication that API is reasonably decent. Documentation
matters as well. And Parnas, the aforementioned Parnas said it much better than I could, so
I'm simply going to read what he had to say. He said, "Reuse is something that is far easier
to say than to do. Doing it requires both good design and very good documentation. Even
when we see good design, which is still infrequently, we won't see the components reused without
good documentation." He said that in 1994. And I don't know about you but, you know,
when I--when I read that I get religion. And the only thing I can do then is to document
religiously. Document every single class, every interface, every method, every constructor,
every parameter and every exception in my public API. Of course all of you do that,
right? And when you go out on the web, whenever you look Javadoc, it's always the case that
every public or protected method has a Javadoc comment, right? No. You know, it's really
terrible because if you don't have a comment telling you what the specification is, what
is the specification? Who knows? You have two choices. Either you guess, in much case
your program probably doesn't work, or you read the code in which case, the implementation
becomes the specification and it's over specified and you no longer have the freedom to change
that implementation at all. So document everything and there are--you know, what does this mean
for classes? Just tell me what an instance of that class represents. From methods, tell
me the contract between the method and its client. That is what must be true before I
call it. What will be true after it returns and any side effects. Those are particularly
important. If you had side effect and you don't document them, people will shoot themselves
in the foot. I'll give you an example of this later. For parameters, people often forget.
Don't just say, "Hmm, the size of the block." It's the size of the block in bytes or in
megabytes or whatever. You've got to tell me what the units are, the form, if it's a
string, especially. I've got to know. Is it XML? You know what form is this string in
and finally, the ownership. If I'm passing an object into an API, do I still only object?
Am I free to modify it after I passed it in or have I transferred ownership from my self
to the object to which I passed that other object? If the thing that you're defining
is mutable, that is, can be modified, then you must document the state space very carefully.
If you have a badly documented space--state space, then you have no hope of being able
to use that API properly. Because people won't know when it's legal to call what and what
will happen after the call is made. I may discuss this elsewhere but an example of how
to do this very badly, our date and calendar, in particular the calendar API in Java. The
state space is almost undocumented and it caused numerous bugs over time. I'm happy
to say by the way, that just days ago, Sun decided that we would be pursuing any JSR
to improve the date and calendar APIs based in part on [INDISTINCT] time and who knows
what or else. But to me--we may finally be, you know, free of that mess. You should consider
performance consequences of API design decisions. And this is funny because this tends to, you
know, contradict the advice. You've all heard that, you know, a premature optimization is
evil. In fact, I have an old essay about that in effective Java. However, that doesn't mean
that you can just ignore performance. Jon Bentley was big on this fact. And in particular,
it turns out that bad API decisions can limit performance. Examples of things that can limit
it are making the type mutable when it should be immutable or vice versa, providing a constructor
instead of a static factory, using implementation type in an API instead of an interface, which
means that people will always have to use that particular implementation even if a better
one comes along later. But the converse of this is, never work an API to gain performance.
Every once in a while you have something that sort of temporarily broken. You know, this
thing is slow and in order to avoid this slow thing, you break your API. The thing that
used to get slow becomes fast but your API is still broken, you know. So, design your
APIs for the long haul. Luckily, good API design usually coincides with good performance.
Here's an example of an API design decision that led to bad performance. In the original
AWT, there was something called a dimension. If you had a component and you ask for its
size, you got back a dimension object, which contained two coordinates. It's simply couple
of longs that were wrapped. The problem is that the dimension object was mutable. Those
longs weren't really wrapped. You know, they were--they were publicly visible, mutable
fields. And what that meant was every time you called getSize, you had to allocate a
new dimension object because otherwise I might, you know, get the dimension object, give it
to you, you might ask for it and then you might modify it modifying his copy as well.
And that would be really bad. Now these two, you know, independent threads or computations
will be tied together in nasty ways. Is that bad? You know, is it really expensive to allocate
a little object containing two longs? No, it's dirt cheap but unfortunately this thing
gets called millions, literally millions of times in a goyap So, you know, all of a sudden,
you're basically allocating megabytes of garbage and that really does cost you. It's garbage
collector pressure that you just don't need. And it was fixed in 1.2 by adding methods
that return each dimension individually as a primitive type which is in fact immutable.
But, you know unfortunately, old codes that used the 1.1 APIs is still slow and will always
be slow. The APIs that you write have to coexist peacefully with the platform. So, that means
that you have to do what's customary for that platform. You have to obey its naming conventions.
You have to avoid anything that's just verboten in that platform. You know, whatever it is,
if you're in java, you know, if you are in C++, everybody knows that there are certain
things that you just shouldn't do or shouldn't use. So, learn what those things are and then
avoid them. And there are--there are generally books that tell you the traps and pitfalls
for every platform. I could recommend one for Java but, you know. Anyway, the thing
you've got to do is mimic the patterns and the core APIs in the platform, because everyone
who uses a platform knows its core APIs. So, if your API feel just like one of the core
APIs, then everyone, because they already know how to use core API will already know
how to use your API. It's as simple as that. And the real trap here is you should never
simply transliterate APIs. That is the worst way to design an API. And what do I mean by
that? Suppose you have a C++ API, and you want a Java version of the same facility.
What you should not do is take every C++ class and make a complementary Java class that contains
all the same methods as the C++ class because what was reasonable in C++ will almost certainly
be unreasonable in Java and vice versa. So, you have to basically take a step back. You
have to say, "What is this class doing and how would I do this in Java?" That's the right
way to do it. Transliterated APIs are almost always broken. Okay. On to class design, I
have 15 minutes to finish the whole--rest of the talk. So that gives me five minutes
each for the next three sections and zero minutes for the last section. So, first of
all minimize mutability. Classes should be immutable unless there's a very good reason
to make them mutable. And the advantages are that the class you get are simple, they are
thread safe, they are--instances of reusable. You never have to generate a new one. The
only disadvantage is that you need a separate object for each value. So, if you a huge object,
let's say a big integer that's a million digits long and you want to throw it away but you
just want to change a last bit, if it were mutable, you could do that in place, you know,
at virtually no cost. But because they're immutable, you have to basically copy a megabit
of data and then throw away the old megabit, which is a little bit unpleasant. But if you
do have to make things mutable, and you often do, you should still make them as immutable
as possible. You should give them a nice small, well defined state space. You should make
it clear--you should make it clear when it's legal to call which method. So, bad examples,
as I mentioned before, are date and calendar. Calendar has this, you know, like the roll
method and when you, you know, you put date into it, who knows what state is behind it?
When it's legal to call what? What state the calendar is in after if you've used it? And,
you know, did you know that when you're using a date formatter, if one thread uses a date
formatter and other thread tries to use at the same time, both threads are hosed. You
know, it kind of feels like an immutable object, except that it has state inside it. You can't
read that state but it's there. You know, so, these are--these are things that are more
mutable than they should be. A good example is TimerTask. It's not immutable, but it minimizes
mutability. In particular, a TimerTask is inherently mutable because it represents an
actual or current computation and that mutates. But, you know, a TimerTask, what you do? You
create it, you schedule it, it runs as many times as it has to run, and then it's dead.
It's gone. End of story. Now, there were people, who asked us, when we were designing the timer
API, "We want to reuse them, you know. It's like expensive to make a TimerTask." And the
answer is, "No, let it die in peace." If you need another one, make another one but by
eliminating that sort of loop from the state space, you make the API much simpler and much
less bug-prone. You should subclass only when it makes to do so. This is the Liskov Substitution
Principle. And it's actually very simple. You just have to ask yourself a question.
When you have two classes, in a public API, and you're thinking of making one of subclass
to another, like through a subclass of bar, ask yourself, "Is every foo a bar?" If you
can answer that with a straight face, then make it a subclass. If not, don't. So, a bad
example is, in Java properties extends Hash table. Is every property's object a Hash table?
Heavens, no. A property's object is a special thing that map certain strings to certain
other string. So, every property's object has a Hash table, perhaps. Typically, it's
implemented a top Hash table but you can't answer that answer--that is a question with
a straight face or what about Stack extends Vector? Is every stack a vector? No. You push
and pump on stacks and that's pretty much all you do them. You might also have a peek
method and a size method. But, you know a vector allows random access, accessing by
index, a stack doesn't. So, it was really wrong to have Stack extends Vector. And the
really bad thing about it was they took away great piece of real estate. We can't use the
name stack for a class that actually does implement stack anymore because the name has
been taken. Good is Set extends Collections. Is every set a collection? Yes, it really
is. Set is just a special kind of a collection. It's a collection that does not allow duplicate
elements. This is a fairly controversial one. But I believe that you should design and document
a class for inheritance or else prohibit it outright, to make the class final or have
no publicly accessible constructor. The reason for that is, is that in heritance violates
in encapsulation in a--in a subtle way. And that is to say that subclassing violates encapsulation
in a way that mere method invocation does not. This is sometimes called the fragile
base class problem, where basically if you have one class, it's implemented atop second
class. And you override a method in the first class. It may modify the behavior of other
methods in the subclass because the original implementations of those methods dispatched
to the method that you just overrode. And then if in the future implementation of the
super class, they re-implement all of these methods, so that this method no longer is
implemented in terms of this one but both of them are implemented in terms of some third
method. Then, you know, this new version of the super class will break the subclass. And
the way to avoid that is either come clean on exactly how every method uses every other
method. That is document the self-use patterns of a class. If you've done that, then you
documented it and designed it for inheritance. So, it's okay to make it non-final. Otherwise,
it could just be final. Now, if you look at the Java API, we got this wrong in many places.
So, most of the concrete class in the J2SE libraries, in particular, collection classes
like, HashMap and HashSet. They're all non-final, but they don't exactly define their self-use
patterns. So, they're a bit fragile. Abstract set and abstract map and the other abstract
xxx classes are good. They really are designed and documented for inheritance. Okay, onto
methods. So, this is--if you remember only two things from the talk today, this is the
second thing. By the way what was the first thing I told you to remember?
>> [INDISTINCT] >> BLOCH: Excellent. When in doubt, leave
it out. The second one is, don't make the client do anything the module could do. Those
two things are like the fundamental rules of API design. So, the worst thing you can
do is write an API that just requires the client to call in and out, and in and out
just doing repeated calls, passing junk from the first call to the second call. These causes
boilerplate code, which as you can see it's red. It's really, really bad. Why is boilerplate
code bad? Because it's an opportunity for bugs, you make boilerplate code by doing cut-and-paste
and then modify. But if you don't do all the modification that you should, it may still
compile but it won't do the right thing. It is ugly, it is annoying, and it's error prone.
And here is a real live example from the W3Cs DOM API. Suppose you have in hand an XML document
and you want to print it to an output stream. That's a very reasonable thing to do. It should
take one call, you know, print, it takes an output stream and you're done. But it doesn't.
Here is what you actually have to do. First you have to import, in addition to w3c.dom,
that's fine and, you also have to import XML Transform, xmltransform.dom and Why? I don't know but I do know you have to do it. And then here's
how the call should've looked. It should've been called Right Doc and, you know, the document
perhaps should have been the receiver and you pass in an output stream and it would
throw an IO exception. But here's what you actually have to do. First, you get a transformer
and what is a transformer? I don't know, but I know you need one. How do you get one? Well,
you take the new instance method on the transformer factory and then that gives you a transformer
factory and once you get the factory you ask for a new transformer. So, as you can see,
they read design patterns. It's great. It's like filled with patterns. So now, you've
got your transformer out of your transformer factory and then you have to set an output
properly on that transformer. You have to set its doc type system to be the doc type
from the doc. I don't know what any of these means but I know you have to do all of it.
And then, you have to get the system I.D. and you have to--that's part of this output
property that you're setting. And then when you're all done, you can actually do the output.
The way you do that is you call transform on the transformer. And you just don't pass
in the doc source because that doesn't implement the right thing. You use the adaptor pattern
here to take your doc and you turn it into a DOM source and then also you can't quite
just pass in your output stream. You wrap it in a stream result and now you get your
output, right? Except for one tiny problem, which is it can throw a transformer exception?
When can it throw it? Well, never actually but the API says it can and it's a checked
exception, so we have to catch it if it gets thrown and then we throw an insertion there
because it can never happen. So, you know, you've got like, whatever it is, six lines
of just unreadable garbage code give you something very simple. If they had started with a used
case, people might want to print their xml documents. You get the idea. Another general
rule when you're designing class--a method is, don't violate the Principle of Least Astonishments.
The user of an API should never be surprised by its behavior. It's worth extra implementation
effort. It's even sometimes worth a reduced performance not to surprise the users of your
API because if you surprise them, what will happen? They'll simply do the wrong thing.
They'll think it does something, it'll actually do something else and their program will be
broken. Here's a real example from the thread API in Java. So, we have this method called
interrupted. You got a thread and you want to check if it's interrupted. You call thread.interrupted.
And what does it do? Well, it tests whether the current thread has been interrupted. Oh,
and by the way, it clears the interrupted status of the current thread. That's just
like a little side effect. It clears the interrupted status of the current thread. Looking at the
name, you know, thread.interrupted, there'll be no way to guess that it does this. But
it does this and many people have, you now, spent hours chasing bugs because of it. You
know what is the primary thing that this call does? It clears the interrupted status. It's
not an unreasonable call but it should've been named clear interrupt status and by the
way, it could've returned the old interrupt status as a favor to you. But they named it
based on--so, the second most important thing it did, instead of the first most important
thing it did. And in doing so, they violated this Principle of Least Astonishment. You
should fail fast. Whenever there's an error, you should tell the user of your API as soon
as possible after the error has happened. Ideally, you should tell him at compile time,
you know, because this way it happens in the lab or in the, you know, here where the program
is being written, instead of out in the field where the program is being run. And that means
that I believe that static typing is a very good thing. It moves errors from runtime to
compile time. I understand that this is another highly controversial topic. But I do believe
it. You know, I've seen it happened. You know, for example, when increased the static typing
in Java by adding generics. I found bugs in preexisting code, you know, because it forced
to me to be more specific about the types that were expected and they told me where
things were wrong. If you are only going to be able to find out an error at runtime, you
want to find out the first time you do something wrong. Like, if you pass some garbage into
something else, you should find out as soon as you pass the garbage in, not ten minutes
later. Here's the example of how not to do it. In the aforementioned properties class
that extends hash table. If you look at the spec, it has properties instance maps strings
to strings. But if you look at this put call, it takes an object key and an object value.
Any object, maybe a string, maybe something else, but it tells you right away if you pass
in something that's not a string, right? If only it were so. It doesn't, in fact, tell
you right away. It lets you put in any garbage you want and then, ten minutes later when
you try--when you call the save call to an output stream, which basically takes his property's
object and translate into some garbage that isn't quite XML. Then and only then does it
blow up with a class cast exception because you put something wrong into it ten minutes
before. But by that time, debugging becomes almost impossible. You don't know where the
call was that put the garbage into your properties object. You should provide programmatic access
to all data that is also available in string form. This is really important. Whenever you
have a method that return something as a string you should also have a method that returns
the same stuff in programmatic form. If you don't do that, then clients will have to parse
the string. Not only is that a pain in the butt but it turns the string into a de facto
part of the API. You can never add information to that string because there's code out there
that pausing that string. And if you change the format of the string, you break that code.
So, what you should do is along with the API that gives you the printable string, you should
have other APIs that give you access to--excuse me, to the actual information and this way
you can add more information to the string later. And in fact, the spec should say that
you are not specifying the format of the string and that anyone who writes code to parse the
string is taking their lives into their hands. So, you know, a bad example is, initially
the only way to get the stack trace in Java was to call this gets stack--sorry, prints
stack trace API and people actually did go parse those things. In 1.4, we finally added
get stack trace API that gives you all the same information, a stack trace element consisting
of the filename, a line number, class name and so forth. But this was a case of sort
of the horse had already left the barn. You should overload with care. Method overloading
can be a good thing but it tends overused. You should avoid ambiguous overloading. That
is multiple overloading that can do different things when passed the same values. And a
bad example, which I am guilty of here myself, is TreeSet has two constructors; one that
takes collection, one that takes a sorted set. The first one ignores the order of the
thing that was passed in. The second one says, "Gee, I'm making a tree set out of another
sorted set, I might as well order it in the same way." Well, here's the problem. If you
have a sorted set that's cast to a collection, then you're calling this constructor and you
get one result. Whereas, if you don't cast it, if you just pass it in, you get another
result, so I really should not have done that way. I should've done a dynamic test. If the
thing was the instance of sorted of set, then I should've preserved the order. So, you know,
the basic rule here is just because you can doesn't mean you should. Often, it's better
to simply give something another name rather than overloading. Overloading can be a real
trap. If you use appropriate parameter and return types that means you should favor interfaces
that were specific classes. You should use the most specific possible input parameter
type. If you accept, let's say, a collection but you'll blow up unless somebody passes
you a set that's broken, you've just taken something that could've been caught at compile
time and instead you're catching it at runtime. Here's a really, sort of, another trap, don't
use a string if a better type exist. In these days of, you know, XML and web services, people
always start off with strings. Strings come in over the web. Just because it started as
a string doesn't mean it should stay as string. You should turn it into something more reasonable
and leave it there. A really bad example of this that I saw in a program years ago, was
a program that passed around a string for its whole duration of its execution, that
was either yes or no. We have a good data type for that it's called boolean. You should
never use floating point types at Google. You guys already know all these but never
use the floating point types, float or double for monetary values. They're not good enough
to represent money. You cannot do exact computations base 10 using floating point numbers--binary
numbers because of the fact that 1/10th is not representable as a binary fraction. So,
don't do it. If you have you know, an amount of money, use big decimal. Perhaps, use long
big integer or what have you but do not use float or double. And when you are faced with
the choice of using float or double, you should almost use double rather than float because,
you know, typically double will run just as fast and, you know, you lose real and important
precision by going down to float. Let see, I'm going to have to just run through the
rest of this. This one is really important. Use consistent parameter ordering across methods.
So, you know, here is an example of what not to do. A real example from Unix. We have two
methods to copy data. One is called store and copy. One is called b-copy. The first
one takes a destination, a source and a size. The second takes a source, a destination and
a size. So, what happens, you know, if somebody assumes one ordering when they call the other
method? They clobber their source data with whatever garbage was in their destination
array. And how long does that take to find that bug? Probably a really, really long time.
This is particularly important when the types of the two parameters are identical because
if you switch them around, you will not know at compile time. A good example here is in
Java.util.Collections, the first value is always the collection being mutated or manipulated.
Similarly, in util concurrent, when you have an amount of time, you always specify it as
a delay followed by time unit never other a way around. And even if it were the other
a way around, the compiler would tell you because these are strong types that are incompatible.
A long and the time unit are two different things. You should avoid long parameter list.
Ideally, you should limit them to three. It's really easy to remember three things especially,
you should avoid long list of identically typed parameters because of the problems that
I've told you before. If you get the order wrong, you're hosed. So here's an example
of what not to do. This is from the Win3 to API, to create a window. You know, if you
look in the middle of these, whatever 15 parameters, you see Int x, Int y, Int end with, Int height.
So, here's, you know, a whole string of Ints and by the way some of these other things
are also Ints, just Int by another name. So, you know, without support from an IDE it's
pretty much impossible to use this API. Luckily, there are number of great techniques for shorting
parameter list. One thing you can do is break up a method into a multiple methods, or you
can create a helper classes to hold the parameters. A specific example of the helper class is
the builder pattern, where if you got a constructor or a static factory that naturally would take
10 parameters, most of which you don't have to specify, most of which have good defaults.
Instead, make a builder and then just plug-in the ones you actually care about and then
call a build method. And that code will be much easier to write and to read. You should
avoid return values that demand exceptional processing. In particular, you should never
return a, "No," instead of a zero-length array or collections. Here is an example of something
we got wrong. The--in the buffered image op-class, we have a method called get rendering hints.
And either, it returns a rendering hints collection or it returns, "No." And what's the consequence
of this? Almost all code that calls this thing is wrong because it rarely returns, "No."
So, people forget to code for that special case. If they do get a no, what happens? No
point or exception and it's completely unnecessary. If they just returned a zero-length collection,
then they wouldn't got into any trouble. So, I think I'm actually going to skip the rest
of the talk because I'm out of time. In fact, I've already used up five minutes more time
than I have. But if you have any other collection--try again. If you have any other questions, I'll
be around for a while afterwards and you can ask me anything you'd like. So, thanks for
coming. Oh, one other thing, which is I have a--I have a give-away for you, which is--when
I gave this at OOPSLA in the proceedings, they gave me two pages to put in a--what do
you call that? Like an extended abstract. And instead of doing that, I tried to do API
design by bumper sticker. I try to basically take this entire talk and break it down into
50 little maxims, like, when in doubt, leave it out or, you know, all programmers are API
designers, each with a sort of a sentence describing, you know, in a bit more detail.
So here, pass this out amongst yourselves, as best you can. And I'll put it up on, you
know--and tell the JJB in case there are more of you than there are piece of paper. So thanks
again for coming.