books chapter eleven
L Peter Deutsch worked on Lisp and Smalltalk for a time, but is probably best known for Ghostscript. He has some great grumpy old man quotes. He’s mostly given up on programming these days, but remains interested in music, which is another way of manipulating symbols.
“My belief is still, if you get the data structures and their invariants right, most of the code will just kind of write itself.” Unfortunately, software development is still a new field. “Which is one of the reasons why so much software is crap: we don’t really know what we’re doing yet.” Seibel tries to suggest that software is fractal, that it keeps getting more complex as we drill down through layers, but Peter disagrees. He makes the case that the kinds of complexity seen at different layers are also different, which doesn’t really fit the definition of fractal. Your understanding of the system at one level doesn’t actually apply at other levels. An interesting point. Are we trying too hard to see patterns where there are none?
On the prospect of everyone becoming a programmer, he relates that tale that in the early days of telephones, the number of operators kept growing. Someone extrapolated the growth and figured that eventually everybody would be working as an operator. And of course, that’s pretty much what happened, although the nature of the job changed somewhat. Maybe someday we’ll all be programmers, but it won’t look like programming today. There’s a large mismatch between the mathematical world of programming and the physical world that surrounds us. “Software is a discipline of detail, and that is a deep, horrendous fundamental problem with software.” Peter says there’s nothing in life like a pointer, which is a pervasive concept in software. I would argue that a credit card number is a real world pointer, but there’s also lots of evidence to suggest people don’t understand credit cards in nearly the same way as cash, so I think he’s right about the mismatch. “I’m kind of an elitist: I would say that the people who should be programming are the people who feel comfortable in the world of symbols. If you don’t feel really pretty comfortable swimming around in that world, maybe programming isn’t what you should be doing.”
Peter doesn’t like being referred to as a coder. That suggests to him too low level work, like a bricklayer, which is a fine job to have, but doesn’t capture aspects of development like design. Nor does he like computer science. “I think very little of it is science in terms of the scientific process, where what you’re doing is developing better descriptions of observed phenomena.”
“Well, my description of Perl is something that looks like it came out of the wrong end of a dog. I think Larry Wall has a lot of nerve talking about language design.” Then Seibel and Peter argue about pattern matching and parsing in Lisp vs Python.
“I never in my wildest dreams would have predicted the evolution of the Internet. And I never would’ve predicted the degree to which corporate influence over the Internet has changed its character over time. I would’ve thought that the Internet was inherently uncontrollable, and I no longer think that.”
Moving on from Ghostscript, we have the inventor of Postscript and founder of Adobe, Charles Geschke. He spent a while at Xerox PARC, where he first developed a desktop publishing system. The demo was in Florida, which required renting two DC-10s to fly the required equipment across the country. Quite the expensive pitch. Of course, the Xerox execs weren’t interested and passed, so he went off to make his own company. He took some investment money to create a computer, printer, and software solution. First he tried to sell to DEC, but they already had computers and printers and just wanted software. Then he tried to sell to Apple, but they already had a computer and printer and just wanted software. Then his investor said, “You idiot, if your customers want to buy the software, sell them the software.” So that’s what they did. They licensed Postscript to Apple for the LaserWriter and the rest is history. Although, that almost didn’t happen either. The Postscript interpreter in the printer required more RAM than was installed in the Macintosh it was attached to, making the printer more expensive than the computer. Lucky for them RAM prices dropped.
Having shipped the infrastructure for printing, Adobe started developing or buying up the software that would convince people to buy printers: Illustrator, Photoshop. (PageMaker was also essential to Adobe and Apple’s early success, but wouldn’t be acquired until ten years later.) It’s kind of funny that early versions of Photoshop ran on black and white Macs. Scanners and digital cameras eventually came down in price from thousands of dollars to something affordable. If you’re going hunting, shoot to where the duck will be.
Funny anecdote: a stock broker insisted on using Courier for his reports. If he used a proportional font, the readers would assume it had been sent out to a professional printer, and was therefore days old information. Something to be said for being so far ahead of the curve people aren’t even aware of what’s possible.
Ann Winblad founded an accounting software company in 1976, sold it, and founded the first VC firm to focus on software. Her first job was working at the Federal Reserve, which at the time was a really cool place to be with the best computers. After a year she decided to start her own company, but needed to borrow $500 from her brother, who was still in high school, to pay for the incorporation fees. So pretty shoestring budgest to start.
This was way back when when software was sold slightly differently. She held a meeting with all (not sure, resellers or computer vendors) and asked them for a $10,000 advance. Of course, none of them carried the company checkbook with them. So she asked them to write a personal check, and then their company could reimburse them, right? A slightly unusual way to do business. But it worked, and she got a bunch of checks, and the company had money. She makes the point that it’s the CEO’s job to make the company succeed, and sometimes that means helping other companies succeed. These other companies could have insisted on negotiations and contracts, but in the end they were only going to sell computers if interesting software was available.
Three related chapters. To quote Shakespeare: “I can write programs that control air traffic, intercept ballistic missiles, reconcile bank accounts, control production lines.” To which the answer comes, “So can I, and so can any man, but do they work when you do write them?”
Some ideas on how to write bug proof programs. The worst bugs are those that come from incomplete or mismatched specifications. Failure comes not from components that don’t work as designed, but from places where the design is incomplete. One important point is to give the specifications to an outside group who will be responsible for testing. Developers are very bad at discovering missing specifications; they will make it up as they go. Brooks advocates the Wirth method of top down design, incrementally refining the specification at each step.
Use a decently structured language. “Further, some have become very doctrinaire about avoiding all GO TO’s, and that seems excessive.” Then he makes another important point about thinking of control structures as control structures and not just program statements. I need to think some more on this, but I like it. When we use a construct like if, are we thinking about the condition variable or the program state that the variable represents? It’s easy to write a loop until the flag is 7, but what does that mean?
We dive into some old school debugging techniques which are mostly obsolete these days. Still, we might think about how we’d test a program if we were working on a mainframe with a limited set of blinkenlights for output. “The cardinal sin was to push START boldly without having segmented the program into test sections with planned stops.” Brooks mentions that interactive debugging in front of a terminal is the way of the future, although he’s also writing at a time when the terminal wouldn’t be in your office. You’d go sit at the terminal, wherever that was, run some tests and gather some output, then go back to your office to think about. He splits his time up between maintaining a journal in his office and typing at a terminal; one hour planning what to do, two hours working at the terminal, then one hour recording the results and what he learned. I thought this was a nice way to maintain discipline while debugging. I tend to run a bunch of tests, and then because it’s so easy, run a bunch more, often duplicating previous work. And everybody has a story about rerunning a test over and over, but using the wrong input or program or command line, etc. Slow down and think.
How do we debug a system? Brooks considers combining a whole bunch of barely finished components together to see what happens vs waiting until every component is thoroughly tested. Surely we can find lots of bugs all at once by doing things the first way? Alas, this probably doesn’t work because when a bug is found nobody knows where it came from. The interactions between multiple buggy programs can be too complex to understand. This advice would seem to conflict with rapid prototyping or tracer bullets style development, but I think there’s a way to make it work. It can be helpful to combine some early components just to make sure that they fit together, the interfaces are well formed, etc., but that’s not a substitute for actually testing components in isolation. On the point of unit testing, I think Brooks was ahead of his time. Plan to write half again as much code for testing as in the product.
When the System/360 hardware was being developed, they used yellow wiring. When a bug was found, they’d patch it with purple wire. When the design change was documented and finalized, they’d replace the purple wire with yellow wire. This is a good technique for software, too. Quick fixes and patches should be marked as such. Code should not ship with purple wires. Alas, software tends to accumulate endless workarounds for workarounds for workarounds. It’s purple wire all the way down.
“How does a project get to be a year late? One day at a time.” We tend to assume we’ll catch up later after there’s a small incidental delay. He actually mentions jury duty, which I liked, because when I was out for two days, that was reflected by the fact I didn’t make any progress for two days, but at no point did we call a meeting and slip the schedule back two days in advance. It simply creeped back one day for each day I didn’t show up. As managers move up the chain, they tend to sweep delays under the rug until they are too big to ignore. This has a cascade effect. A good team needs a combination of hustle and a sense of where the critical paths are. It’s easy to slack off if some other team is behind schedule, but without a detailed plan it can be hard to adjust.
On the subject of documentation, you should write it, and lots of it. Some specific tips. What does a program do, where does it run, how does the user know what’s expected and unexpected? He makes a good case that users should be provided with test cases. To expand on his previous testing advice, he offers some practical guidelines for tests to include. A few tests that demonstrate basic functionality. Some barely legitimate tests for largest and smallest inputs, etc., to make sure the program doesn’t break. Some barely illegitimate tests on the other side to make sure that input validation is working and errors are properly reported.
You can make a flow chart of your program but you probably don’t want to. Use variable names that convey meaning. Another idea is to include comments, right there in your source code, although this does increase the amount of disk space required to store a program. Fortunately, I think we’re past worrying about that.
Some tips on planning and specifications.
36. Gathering requirements is not like gathering berries. You can’t just walk around and pick them off the ground. Know the difference between requirements and policies. A lot of requirements may initially be provided as policies, which should be generalized to avoid a great deal of difficulty later. The example given is access control. Records may only be accessed by supervisors sounds like a requirement, but it’s a policy. The requirement is records may only be accessed by authorized users. Later, when the policy is changed to allow access to auditors who are not supervisors, this will matter. “Requirements are not architecture. Requirements are not design, nor are they the user interface. Requirements are need.”
Track requirements to avoid creep. When the project is inevitably late, you’ll want to know why. I’d add that it may be helpful to keep requirements in the form of an append only log. There can be a condensed version describing the current state, but a long form chronological log should be readily available. The length of the log reflects the true size of the project.
37. Some problems are really hard, seemingly impossible. The pop advice to think outside the box isn’t really helpful. Instead what we want to do is find the box. The problem is probably impossible because it’s poorly defined. Or possibly because the scope of potential solutions is too narrow.
Having built our own computer, we’re going to turn back the clock, way back, and take a whirlwind tour of computers from abaci to chips. Counting with beads or pebbles goes back thousands of years. Scottish mathematician John Napier didn’t like multiplication, so he invented logarithms to make things easier, leading to forward and reverse lookup tables and eventually culminating in the slide rule. Those tables were calculated by hand, and error prone, so Charles Babbage decided to build a machine, the Difference Engine. That was never finished. Neither was the programmable successor, the Analytical Engine, although Ada Lovelace was able to consider its design and invent nested loops, only to be forgotten for a century.
Practical advances in computer technology would be driven by the US Census. The 1880 census took seven years to tabulate, and it was estimated that the 1890 census wouldn’t be done until after the next census. And so the Census Office hired Herman Hollerith to make a machine. It stored information on 24 column punch cards, and counted them automatically, using pins and mercury pools and electromagnets and hatches and wheels. The Tabulating Machine Company he founded was later renamed by its president, Thomas Watson, to International Business Machines, and the original “Hollerith cards” grew to be 80 columns wide as God intended.
Elsewhere, Harvard grad student Howard Aiken, working with IBM, created the Mark I, a digital computer that printed tables. The Mark II was the largest computer made from electromechanical relays, with 13,000 relays. Relays were quickly replaced with vacuum tubes, which could switch on and off many times faster, leading to the Enigma breaking Colossus computer and the 18,000 tube, 30 ton ENIAC. John von Neumann worked on the successor to the ENIAC, the EDVAC, which was the first stored program computer. Thus, we call computers that store the program in memory the von Neumann architecture. (Curiously, the book doesn’t mention that the term Harvard architecture comes from the Mark I.) The EDVAC didn’t use vacuum tubes for memory (too expensive) or iron cores (invented later). Instead it used mercury delay line memory, which consisted of a long tube filled with mercury. It took one millisecond for sound to traverse the tube, so by sending pulses one microsecond apart, the tube could hold 1000 bits of information. That must have been fun to work with.
Claude Shannon, who we met before, discovered the relationship between switches and Boolean logic, and then went on to establish the entire field of information theory. Norbert Wiener invented the cyber. The UNIVAC was developed from 1948 to 1951, and appeared on national TV with Walter Cronkite, who called it an “electronic brain”. Over at Bell Labs, on December 16, 1947, John Bardeen and Walter Brattain and William Shockley invented the first semiconductor transistor.
So now we’re going to break for a physics lesson. Semiconductors work by being doped with particular impurities, and if we combine them in the right way, we get all sorts of cool solid state electronics. Like the pocket radio. And thus the era of the vacuum tube was brought to an end. Transistors were smaller, cheaper, and cooler. Wouldn’t it be cool if we could combine lots of transistors together? Indeed, we can. Jack Kilby and Robert Noyce both had this idea. In 1965, Gordon Moore predicted everything would double.
For convenience, integrated circuits are packaged up and wrapped in plastic, with two rows of pins on each side. The Dual Inline Package. Some ICs are transistor-transistor logic (TTL) and some are complementary metal-oxide semiconductor (CMOS). Generally this affects their power consumption and voltage ranges and switching speeds, but some of the differences have faded with time. Transition time has always been very important, because that’s what makes computers go. A nanosecond is very short. Probably shorter than you can really comprehend. This matters because a computer processor is, plainly put, “moronically simple” moving bytes back and forth. In order to get any real work done, it needs to complete these simple tasks billions of times per second. If you combine a bunch of ICs together, instead of just being collections of NAND gates and flip flops, you have a microprocessor.
Now we’re going to look at two classic microprocessors. Not the very first, which would be the Intel 4004, but its successor the 8008 and the Motorola 6800. And by look at, I mean we’re going to go through the entire 8008 instruction set, opcode by opcode. In theory, an 8-bit computer might have 256 different instructions, and that would be a lot indeed, but lucky for us the 8008 only has 244 instructions. The interesting parts here are that in addition to a dedicated accumulator, we have other registers, B, C, D, H, and L. Instructions to move between them. Direct addressing, indirect addressing, immediate addressing. Instructions for binary coded decimal, rotating, xor, and other Boolean operations. But still no multiplication. Instructions for pushing and popping values on to a stack. Jumps, and more importantly, call and return instructions. Conditional call and return instructions because why not. Input, output. And finally, 00, the NOP.
The 6800 is a lot like the 8008, so we won’t look too closely. It has similar instructions, just with different opcodes. And wait, what’s this? It encodes 16 bit values backwards! The war between little endian and big endian has been with us from the beginning.
The 8008 was used in a few computer designs, but most importantly evolved into the 8088 (by way of the 16-bit 8086) which was the processor selected for the IBM PC. The Apple II used the MOS 6502, a refinement of the 6800, and the Macintosh used its successor, the 68000 or 68K.
Since these computer were fully functional, even by today’s standards, what do we do with several decades worth of doubling transistor counts? The answer pipelining, and speculative execution, and cache, and lots of other tricks to make things go fast.
I like the purple wire plan. Even when workarounds are documented (unfortunately rare), it’s usually just a comment right in the source next to the workaround. That’s a good means of explaining the workaround, but a poor means of documenting its existence. There’s no plan to deprecate, no timeline. It will remain there until someone stumbles upon it. It would be amazing to see a project collect and document every workaround and its life expectancy in a top level document, right next to the README.