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

It doesn’t matter how good you are; you can’t get an API right until you’ve tried to code to it. You design something; try to use it; and say, ”Oh, this is so wrong.” And if you do this before you’ve wasted time writing all of the layers underneath it, that’s a huge win. So what I’m talking about is test-first programming and refactoring the APIs, rather than refactoring the implementation code underneath the APIs.

As far as doing the simplest thing that will work, I’m all for it. The fundamental theorem of API design is, when in doubt, leave it out. It should be the simplest thing that is big enough to handle all the use cases that you care about. That doesn’t mean “Just throw some sloppy code together.” There are oodles of aphorisms to this effect. My favorite is one that’s commonly misattributed to Thelonious Monk: “Simple ain’t easy.”

Nobody likes sloppy software. People who say, “Write the simplest thing that could possibly work and refactor mercilessly” aren’t saying, “Write sloppy code,” and they aren’t saying, “Don’t do upfront design work.” I’ve talked to Martin Fowler about this. He’s a huge believer in thinking about what you’re going to do so your system has a reasonable shape and a reasonable structure. What he’s saying is, “Don’t write 247-page specs before writing a line of code,” and I agree.

I do disagree with Martin on one point: I don’t think tests are even remotely an acceptable substitute for documentation. Once you’re trying to write something that other people can code to, you need precise specs, and the tests should test that the code conforms to those specs.

So there are some points of disagreement between the two camps, but I don’t think the gulf is as wide as some people do.

Seibel: Since you mentioned Fowler, who’s written a couple of books on UML, do you ever use UML as a design tool?

Bloch: No. I think it’s nice to be able to make diagrams that other people can understand. But honestly I can’t even remember which components are supposed to be round or square.

Seibel: Have you ever done full-on literate programming a la Knuth?

Bloch: No. I’m not against it in principle. I just haven’t had occasion to do it. The other thing is—how can I put this delicately—I tend not to buy into religions, any religions, whole hog. Whether it’s object-oriented programming or functional programming or Christianity or Judaism—I mine them for good ideas but I don’t practice them in toto. There are a lot of great ideas in literate programming, but it’s not the right bar: there aren’t enough other programmers hanging out there. I could see maybe doing it once as an experiment.

What I do instead is I will cheerfully spend literally hours on identifier names: variable names, method names, and so forth, to make my code readable. If you read some expression using these identifiers and it reads like an English sentence, your program is much more likely to be correct, and much easier to maintain. I think that people who say, “Oh, it’s not worth the time; it’s just the name of a variable,” just don’t get it. You’re not going to produce a maintainable program with that attitude.

Seibel: One way that programs differ from most literature—nonexperimental literature anyway—is that there is no one order in which to read a program. How do you read a big program that you didn’t write?

Bloch: Good question. The truth is I really want programs to be wellwritten. I know a few people with the ability to take an arbitrarily large and poorly written system and wrap themselves in the code till they get a total mental picture of the architecture. It’s a really useful skill, but I’ve never been able to do it.

I want to be able to take small modules, read them, and understand them in isolation. If I’m trying to read a system that’s tightly coupled so I have to read the whole thing in order to understand one part, it’s a nightmare. I have to psych myself up even to attempt to read it, and I have to have access to all the code at the same time. I usually print everything out and sit on the floor surrounded by the printout, writing notes on it.

If I’m reading a well-written piece of code, I try to find a view from 10,000 feet: usually someone, somewhere has written a description of the shape of the entire system. If I can find it, I know what the important modules are, and I read them first, occasionally diving down into lower-level modules to aid my understanding.

Also, although code is written linearly down the page, the execution is not at all linear. If I’m lucky enough to have a piece of code that can be read from top to bottom, great. If not, it’s important that I have access to tools that let me quickly locate methods that are being invoked, classes that are being extended, and so on. This lets me understand key execution paths through the code.

Seibel: Do you ever step through code as a way of understanding it?

Bloch: Absolutely! That is still my chosen method of debugging. Especially for concurrent code—there are too many states that the thing can be in for me to possibly enumerate all of them. I just stare at the code; step through it mentally; think of what invariants must hold at what time. For all of the fancy debugging tools at our disposal, there’s nothing that can match the power of simply stepping through a program, in a debugger or by reading it and mentally executing the code. I’ve found many bugs that way and I use it as part of the writing process.

As I write the program, I say to myself, what it is that must be true here? And it’s very important to put those assertions into the code, to preserve them for posterity. If your language lets you do it with an assert construct, use it; if not, put assertions in comments. Either way, the information is too valuable to lose. It’s what you need to understand the program six months down the road, and what your colleague needs to understand the program any time at all.

Seibel: Do you feel like people understand invariants and how to use assertions as well as they ought?

Bloch: No. You probably know that assertions were the first construct that I added to the Java programming language and I’m well aware that they never really became part of the culture. Only a small fraction of Java programmers use them. I don’t exactly know why that is. Talking of mathematics—invariants are very much a mathematical idea.

Seibel: But you don’t have to have a lot of math to be able to understand it.

Bloch: You don’t. But let me just play the devil’s advocate. There’s a certain precision of thinking that comes with doing math. I coached a Math Olympiad team for fourth and fifth graders. This is just the age at which some kids are starting to understand, at some level, the notion of a proof—that a proposition can be demonstrably, unequivocally true rather than just, “I think it’s true because here are a few examples where it seems to work.” In order to understand the notion of an invariant, you have to understand the notion of a proof. Unfortunately, there are plenty of adults who don’t. And it’s a style of thinking that is typically taught in mathematics classes.

Seibel: You’d almost wonder if maybe the better forum to teach that kind of thinking would be in programming. If you just taught programming as being about invariants—

Bloch: To a certain extent I agree, but you can go too far in that direction. Then we’re back to Dijkstra. I’m sure you’ve read “On the Cruelty of Really Teaching Computing Science”, which I think is as wrong as it could possibly be. Dijkstra says that you shouldn’t let students even touch a computer until they’ve manipulated symbols, stripped of their true meaning, for a semester. That’s crazy! There’s a joy in telling the computer to do something, and watching it do it. I would not deprive students of that joy. And furthermore, I wouldn’t assume that I could—computers are everywhere. Ten-year-olds are programming.