Ken Arnold has done a lot of design in his day. While at Sun Microsystems, Ken was one of the original architects of Jini technology and was the original lead architect of JavaSpaces. Prior to joining Sun, Ken participated in the original Hewlett-Packard architectural team that designed CORBA. In Part I of this interview, which will be published in six weekly installments, Arnold explains why there's no such thing as a perfect design, suggests questions you should ask when you do a design, and proposes the radical notion that programmers are people.
Bill Venners: I once heard you say there is no such thing as a perfect design. Could you clarify what you meant by that?
Ken Arnold: There is no such thing as a perfect design for a couple of reasons. One is that all designs take place in context. You are trying to solve a particular problem. Although you might state in a general way, "I am trying to put together a linked list container," the fact is that linked list will be used in a particular context. And whatever design trade offs you choose to make the linked list reasonable for that context will be bad for other contexts. For example, in your context, are there more insertions than deletions? Is there more reading than modification? Depending on the answers to those questions, you might make certain optimizations rather than others.
Another issue is who will be programming with your linked list. Are you giving it to a group of rocket scientists who you can trust will do things right 98 percent of the time? Of course, you'd be wrong, but you could hope they'd be right most of the time. Are you handing it to relatively inexperienced people? Or are you handing it to people who, despite being very experienced, aren't focused on how to use the linked list? Instead, they are focused on some other problem, so their understanding of the linked list will be casual. If so, you have to design to prevent errors. Designing to prevent errors can make your job more difficult. You may have to trade off error prevention against functionality, or at least ease of use. So part of the reason no perfect design exists is that no matter what you do, you end up making one design trade off over another. You make choices that are right for your context, but bad for some other set of circumstances.
Another reason, which is in a different category, is that if you try to create a perfect design you will expend a huge amount of effort. You will go through all sorts of behavior patterns. You and the architects meet and think about this perfect design mightily. And you argue for hours over, say, whether
next should be synchronized. It is a vast sink of time. The difference in effort between delivering something perfect, as opposed to something good, is significant. It just isn't worth the trouble in 98 percent of the cases.
And then there's the problem of predicting the future. Whatever you design for, whatever set of trade offs you choose, no matter how hard you work at it, your linked list will be used in ways you didn't expect. Perhaps you thought there would be more modifications than reads, but it turns out you were wrong. If you over commit to any of those directions, you can hurt your design.
The best that people can reasonably hope for is to put forth an appropriate amount of effort and get a good design that is sufficient. Your design should do as little as it can to get the job done so you can add the right thing when you discover what that right thing is.
Bill Venners: One thing that impresses me about the
JavaSpace interface is that it has only seven methods. It is very simple, and yet I can do a lot with those seven methods. How did you achieve an interface design that was both simple and powerful?
Ken Arnold: With JavaSpaces, in particular, we had the great of advantage of leveraging off of the Linda work. We started with Linda's simple design as a launching point. It was easy to see that we didn't need to do X, Y, Z, because they could be done on top of the API. Maybe someone would want to write an API that does those things on top of JavaSpaces, but those things didn't need to be in the
There is an important distinction between, "What does this tool do well?" and, "What can you write that uses this tool to do something else well?" When I design, I try to make each actor within the design—each class or interface, for example—do one thing right. I also try to never couple what isn't intrinsically coupled. This is a general design principle.
For example, look at what happens in, say,
ServiceRegistrar, Jini's lookup service API. I don't remember the number of methods it has, but it is comparable to the
JavaSpace interface, maybe ten methods. And the Jini group didn't have other work, except in principle, to reference. But
ServiceRegistrar comes from that same design principle. RMI is another example. It is relatively simple to write an RMI [remote method invocation] system compared to the traditional CORBA RPC [remote procedure call] systems. I think that comes from focusing on what the user needs to do, rather than what the user might want to do.
Many people approach designs by asking, "What might the user want to do?" If you start with that question, the set of answers is infinite. You end up with interfaces that have 80 or 90 methods, or 50 or 60 interacting classes. Look at Swing's
JComponent class, for example. It has well over 200 methods.
JButton, as a
JComponent subclass, inherits those methods. But someone dealing with a button cares about five of those 200-plus methods most of the time. The other methods are interesting in odd cases, so they should be set aside somewhere. But they're all in there together and you don't know what to touch.
There is a phenomenon I call surface area of design, which is what you must understand about a design to know which part you care about. Does the fact that there are more than 200 methods mean anything to the
setText() method? You must know something about those 200 methods to know the answer to that question. You have to know what to ignore. Many people say they don't need to look at all those other methods. But you do need to know if they will interact with the methods you do care about. The problem is you need to know enough about those other methods to know you don't need to understand them.
Bill Venners: Simplicity is a design virtue, but can a design be too simple? If you eliminate some feature in the name of simplicity that is truly useful to most users, you've actually made their jobs more difficult. Since you didn't provide that feature, they have to build it themselves or do without it. How do you know how simple to make things?
Ken Arnold: Yes, at some point a design is so simple it doesn't work anymore, right? I think the fundamental way you get simple, yet sufficient, systems is to ask yourself some questions. First, what does the user need to accomplish? Let me back up and explain what I mean by user. I have this really weird radical notion. Every great idea starts off with an absolutely radical notion. I am immodest enough to think I have this great idea. And the radical notion that founds it is that programmers are people. Now if you accept this premise, then the next step is to say that designing tools for programmers, including languages, APIs, and compilers, is a human factors problem. And so we should ask the same kinds of questions that people ask about GUIs [graphical user interfaces]. Is it easy to do what you need to do? Is it natural? Are simple things simple? Are complicated things complicated? Are dangerous things hard to do? Are common things easy to do? Are similar things done in a way that is naturally similar to the person?
You start asking all these questions. And if you do that right, like with a GUI, you come up with something easy to use. It may be rich and complicated, but it has an easy starting point and easier mechanisms to learn things.
However, the more common way to think of design is, "What can I, the designer, do?" instead of, "What does the user want to do?" For example, we could have done many things with JavaSpaces, but our capabilities were of no value. It is the user's desires that were of value.
The questions I fundamentally ask are: What do users want to do? What do they have in hand when they want to do it? What kinds of information do they have to accomplish this task? And how do they think of this task?
Also, since my system will have more users than implementers, it is quite all right to make the implementer's life complex to make the user's life simple. People often say, "Oh, I will design it this way, because I have this kind of data structure and that kind of data structure, so I'll just allow people to merge them together to get this result." But the real question is what does the user want? It may be easy for you to put these two data structures together and hard for you to do what the user wants. But if you don't do what users want, then they will have to do it themselves. If they do it themselves, then you can expect they will do so 1700 different times on top of your API with more bugs and more complexity. The real reason RMI is easy compared to CORBA is because of that attitude, putting what users want first. They want to invoke a method. Okay, how do you make invoking a method as natural as possible without pretending things that aren't true? Well, they want to pass in actual objects as parameters. OK, but the runtime figures out how to get objects to the other side, not the programmer.
Bill Venners: That fits with how I think about design. The first guideline in my book Object Design is: Design objects for people, not for computers. I talk about programmers as users and APIs as user interfaces. I make the point that you are not just designing software for a computer to execute, but also for other programmers to understand and use. I've found that the main thing that makes a design good is that it is easy for people (other programmers) to understand and use.
Ken Arnold: It takes a lot more work to understand the other person than it takes to understand you. You might say, "I have these two things together so I will let the user—the person using the API—manipulate them." But that user didn't say, "Hey, I want to manipulate these two data structures!" Often the user is saying, "I want to get this result." If users could get a result without manipulating the data structures, they'd be happy as clams. If you can make it more natural for them to get that result, the fact that you have to go through 10 times as much work to access those data structures is good; it means you are providing value. Many people are much more likely to think about what they have in hand and what they can do. They think from the implementation out, instead of thinking from the user in.
To me, thinking from the user in is the designer's job. If you just want to implement, you should work for somebody who knows how to design. If you want to design, you have to ask, "Who am I designing for? What do they want to accomplish? And what do they have at their disposal to accomplish it?"
Does the user already think of this parameter as an image or a data stream? If the user thinks of it as a data stream and you think of it as an image, then that is your problem not theirs. They should hand you a data stream, and you should do whatever you need to do given a data stream.
Or maybe it is the other way around. If you look at Jini, many times you will hand something to the Jini APIs that can't be transmitted across the network as is. Some methods of the lookup service, for example, take real
Class objects. But you don't want to move
Class objects around the network in that form. Everyone who implements the lookup service, therefore, must translate the
Class objects into something that can be moved around the network. Well, that is just their problem.
That means the user doesn't have to call something that turns a
Class object into something transmittable. And not only is that easier on the user, but it allows more variation in the implementation, because you can have one kind of transmitter but I can have another. And the whole issue of transmittability is an implementation detail.
Design fundamentally to me it is a human factors experience. And, yes, these humans are programmers. And they are a little different than other people. They may be better at formal thinking. Maybe they remember more than the standard seven plus or minus two. Maybe they can remember nine plus or minus two, or eight plus or minus three, who knows. But they are still people.
Another thing to keep in mind is that people will do stupid things. To you, everything you write is important. To the person using that design, it is a means to an end, and they want to understand as little of it as possible. As a result, you want to make illegal or improper things impossible. Any mistakes you can't make impossible, you want to catch. In other words, first design such that improper things can't even be written or expressed. They are impossible to do. Errors you can't make impossible, you want to catch right away. So as soon as a user makes a mistake he or she is told.
Don't give people a method that does something error-prone. Give them the method that allows them to do the subset that is not error-prone. Suppose you have a file object on which you can call
close. You can only close an open file. You shouldn't be able to call
close on the file object, because it may not be open. The
open method should return something on which you can invoke
Therefore, you can't invoke
close on something you have never opened, because the only thing you can invoke
close on you get from
open. Now you can't express the error. So you don't have to catch the error of attempting to close an unopened file, because you can't even write that.
Bill Venners: Those sound like the things you need to think about when you design the way a human interacts with a car or a power plant. You want to make common things simple. And you want to make it hard for people to make mistakes.
Ken Arnold: Sure. Because people will make mistakes. The one thing you are absolutely certain of is they will make mistakes. It doesn't matter how well you train them. Even if they work in the office next to you, even if they are you six months from now, people make mistakes.
The Jini Community, the central site for signers of the Jini Sun Community Source License to interact:
Download JavaSpaces from:
Design objects for people, not for computers:
Make Room for JavaSpaces, Part I - An introduction to JavaSpaces, a simple and powerful distributed programming tool:
Make Room for JavaSpaces, Part II - Build a compute server with JavaSpaces, Jini's coordination service:
Make Room for JavaSpaces, Part III - Coordinate your Jini applications with JavaSpaces:
Make Room for JavaSpaces, Part IV - Explore Jini transactions with JavaSpaces:
Make Room for JavaSpaces, Part V - Make your compute server robust and scalable with Jini and JavaSpaces:
Have an opinion? Be the first to post a comment about this article.
Bill Venners is president of Artima Software, Inc. and editor-in-chief of Artima.com. He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platform's architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Bill has been active in the Jini Community since its inception. He led the Jini Community's ServiceUI project that produced the ServiceUI API. The ServiceUI became the de facto standard way to associate user interfaces to Jini services, and was the first Jini community standard approved via the Jini Decision Process. Bill also serves as an elected member of the Jini Community's initial Technical Oversight Committee (TOC), and in this role helped to define the governance process for the community. He currently devotes most of his energy to building Artima.com into an ever more useful resource for developers.