Изменить стиль страницы

But there’s a discrete and a continuous way to get your mind around infinity. I think that for a programmer it’s more important to have mastered the discrete way. For example, I just mentioned induction proofs. You can prove something true for all integers. It’s kind of magical. You prove it for one integer and you prove that one implies the next and then you’ve proved it for all of them. And I think that is more important for a programmer than, let’s say, understanding the notion of limits.

Luckily we don’t have to make a choice. I think that there’s plenty of room in the curriculum for both. So even if you’re not going to use the calculus as much as you use the discrete mathematics, I think it should still get taught. But I think that the importance of the discrete stuff is greater than that of the continuous.

Seibel: You talked before about how writing prose has many similar characteristics to programming. While mathematics has always been closely associated with computers and programming, I wonder if once you’re talking about developing things like web frameworks or a web application on top of a framework, if it requires skills more related to writing.

Bloch: Yes—earlier you mentioned that there were two distinct communities of Java programmers. The need for math is much greater in the community that writes libraries, compilers, and frameworks. If you write web applications on top of frameworks, you have to understand communication, both verbal and visual. I get infuriated at web sites when they drive me to do the wrong thing. It’s clear that someone just hasn’t thought about how someone approaching this thing will deal with it. So yes, the truth of the matter is that programming is at the confluence of a whole bunch of disciplines. And depending on which ones you excel at, you will be better at writing different applications. But even libraries, compilers, and frameworks have to be readable and maintainable. I contend that you’ll have a hard time achieving that goal if you aren’t a competent writer.

Seibel: What is your process for designing software? Do you fire up Emacs and start writing code and then move it around until it looks right? Or do you sit down on your couch with a pad of paper?

Bloch: I gave a talk called “How to Design a Good API and Why It Matters” at OOPSLA a couple years ago, and several versions of it are floating around the Web. It does a pretty good job explaining how I go about it.

The most important thing is to know what you’re trying to build: what problem you’re trying to solve. The importance of requirements analysis can’t be overstated. There are people who think, “Oh, yeah, requirements analysis; you go to your customer, you say, ‘What do you need?’ He tells you, and you’re done.”

Nothing could be further from the truth. Not only is it a negotiation but it’s a process of understanding. Many customers won’t tell you a problem; they’ll tell you a solution. A customer might say, for instance, “I need you to add support for the following 17 attributes to this system. Then you have to ask, ‘Why? What are you going to do with the system? How do you expect it to evolve?’” And so on. You go back and forth until you figure out what all the customer really needs the software to do. These are the use cases.

Coming up with a good set of use cases is the most important thing you can do at this stage. Once you have that, you have a benchmark against which you can measure any possible solution. It’s OK if you spend a lot of time getting it reasonably close to right, because if you get it wrong, you’re already dead. The rest of the process will be an exercise in futility.

The worst thing that you can do—and I’ve seen this happen—is you get a bunch of smart guys into a room to work for six months and write a 247-page system specification before they really understand what it is they’re trying to build. Because after six months, they’ll have a very precisely specified system that may well be useless. And often they say, “We’ve invested so much in the spec that we have to build it.” So they build the useless system and it never gets used. And that’s horrible. If you don’t have use cases, you build the thing and then you try to do something very simple and you realize that, “Oh my gosh, doing something very simple like taking an XML document and printing it requires pages upon pages of boilerplate code.” And that’s a horrible thing.

So get those use cases and then write a skeletal API. It should be really, really short. The whole thing should, usually, fit on a page. It doesn’t have to be terribly precise. You want declarations for the packages, classes, and methods and, if it’s not clear what they should do, then maybe a onesentence description for each. But this is not documentation of the quality that you will end up distributing.

The whole idea is to stay agile at this stage, to flesh the API out just enough that you can take the use cases and code them up with this nascent API to see if it it’s up to the task. It’s just amazing, there are so many things that are obvious in hindsight but when you’re designing the API, even with the use cases in mind, you get them wrong. Then when you try to code up the use cases you say, “Oh, yeah, this is fundamentally wrong; I have too many classes here; these should be combined, these need to be broken out,” whatever it is. Luckily, your API doc is only a page long, so it’s easy to fix it.

As your confidence in the API increases, then you flesh it out. But the fundamental rule is, write the code that uses the API before you write the code that implements it. Because otherwise you may be wasting your time writing implementation code that won’t get used. In fact, write the code that uses the API before you even flesh out the spec, because otherwise you may be wasting your time writing detailed specs for something that’s fundamentally broken. That’s how I go about designing stuff.

Seibel: And how specific is this to designing things like the Java collections, which are a particular kind of self-contained API?

Bloch: I claim it’s less specific than you might think. Programming of any complexity requires API design because big programs have to be modular, and you have to design the intermodular interfaces.

Good programmers think in terms of pieces that make sense in isolation, for several reasons. One is that you, perhaps inadvertently, end up producing useful, reusable modules. If you write a monolithic system and, when it gets too big, you tear it into pieces, there will likely be no clear boundaries, and you’ll end up with unmaintainable sewage. So I claim that it’s simply the best way to program, whether you consider yourself an API designer or not.

That said, the world of programming is very large. If programming for you is writing HTML, it’s probably not the best way to program. But I think that for many kinds of programming, it is.

Seibel: So you want a system that’s made up of modules that are cohesive and loosely coupled. These days there’s at least two views on how you can get to that point. One is to sit down and design these intermodule APIs in advance, the process that you’re talking about. And the other is this “simplest thing that could possibly work, refactor mercilessly” approach.

Bloch: I don’t think the two are mutually exclusive. In a sense, what I’m talking about is test-first programming and refactoring applied to APIs. How do you test an API? You write use cases to it before you’ve implemented it. Although I can’t run them, I am doing test-first programming: I’m testing the quality of the API, when I code up the use cases to see whether the API is up to the task.

Seibel: So you write the client code to use the API and then look at it and ask, “Is this code I would want to write?”

Bloch: Absolutely. Sometimes you don’t even get to the stage where you can look at the client code. You try to write it and you say either, “I cannot do this at all because I forgot this piece of functionality in the API,” or, “I can do this but it’s going to be so tedious that this was not the right approach.”