books chapter eight
Peter Norvig has been around for a long time, writing lots of code. Back in the day, a lot of programming was done solo, and you wrote everything from scratch. Now with something like Ruby on Rails, it’s very important to understand the interfaces rather than the insides.
Peter doesn’t really like pair programming much, although he has an interesting alternative. Two people discuss a problem for a while, then independently write code, then they swap positions and test each other’s code. This sounds like a good way to keep output high, but also benefit from lots of review and it ensures that there’s always two people who understand any piece of code. We talk a bit about the IBM master-programmer idea, Brooks’s surgical team, which Peter initially calls the dumbest idea ever. “Why would anybody want to subject themselves to being a gopher for the one real programmer?” Indeed, nobody wants to be (or admit to being) anything less than the best. But maybe it works if it’s part of an apprenticeship? In particular, debugging skills are rarely taught formally, but picked up along the way. You could learn much faster by watching an experienced debugger work than by stumbling along oneself.
What does it mean for a program to be correct, at least in the formal logic sense? Are the first ten results Google returns for a search provably the best ten results? “I think once you start solving these types of problems or the problem of having a robot car navigate through a city without hitting anybody, the logical proof gets thrown out pretty quickly.” So that actually sounds kind of ominous. Not to mention topical. I’d really prefer that your self driving car not run me over, but who knows what the neural nets are thinking?
Peter is another fan of small programs. If you can keep the whole idea in your head, that’s more likely to work than a larger program. He also recommends reading some algorithm books, so you know what choices are available for problem solving. He mentions a few times you need to know about backtracking and dynamic programming to solve certain problems, and without that knowledge you’re going to have a very hard time. I think this makes a lot of sense. Every problem has a certain shape, if we step back and look at it, and then the solution is likely to be already known. This requires both that we be able to see the entire problem and that we have knowledge of available solutions.
One time Peter was at Heathrow airport and the power was out. But nevertheless things kept working because somebody had a printout of all the flights. So a robust program keeps working even when the power is out. Though I think Heathrow has since “upgraded” to a new system that no longer works so well when the computers are down.
The Mars Orbiter crashed because one team used metric units and another team used awesome units. And mostly, because they failed to communicate. But really, because some code was reused. The original library, written for a previous mission, generated some data for a log file. Then for this mission it was reused and the log data was fed into the navigational system. Which effectively upgraded a not critical component to really critical status, but that change kind of slipped through.
Throughout the interview, Peter returns to the idea that software only needs to be good enough. If it’s 90 percent, and you ship something, that provides more benefit to more people than waiting until it’s 100 percent and delivering 0 in the mean time. And then you can go on to other projects. It might be possible to bring ten projects up to 90 percent in the time it takes one project to reach 100. But to return to the point that most software today consists of gluing together other software, I’ll do some math and note that 90 percent to the 10th is only 35% done. Which pretty much matches my observation of real world software.
Paul Graham put Lisp on the web. Actually, he started trying to put art galleries online, but art galleries weren’t interested in being online. But then they switched to online storefronts, which people were interested in.
In the beginning they were desperate for users, because everybody could see how many users they had. An interesting hurdle for web software. People take one look and decide to pass because nobody else is using it, and so a lot of effort that could make the product better is instead spent attracting users. With downloadable software, nobody can see the userbase, so they’re more likely to judge the software on its merits.
Eventually they were bought by Yahoo and integrated. Paul’s analogy is that they were integrated the same way that a sugar cube is integrated into a cup of tea. The separate identity of Viaweb was completely dissolved.
Viaweb also had a power outage. But instead of using printouts, they bought a generator. It was too noisy to run in the office, though they tried, so they put it on the sidewalk outside and ran extension cords into the office. That is also a way to keep running when the power is out.
Joshua Schachter started delicious. A history of the past ten years of delicious would make for a very interesting read, but today we’re just here to talk about the early days. A lot like Yahoo itself, the early predecessor to delicious was just a list of links being personally maintained by Josh. As he describes it, it was single player. Then after noticing he had thousands of subscribers, he started adding multiplayer mode.
Because it was a small personal project to start, and he only worked on it in his spare time, he managed the codebase by keeping every function to one screen of code. That way, he could sit down and improve something without rereading the entire codebase. Just make a series of small isolated changes over the course of a few years and gradually things get better.
How much does a large program cost? Not to build, but to run? If you’re renting memory for $12 per kilobyte per month, running a large program can be quite expensive. On the other hand, features require space. We as users can’t demand software that does more without providing it with more resources.
A good program manager will establish some limits for size, but beware false economies. It’s easy to reduce size by shedding responsibility, but eventually somebody will have to take up the task.
The particulars of this chapter are somewhat dated, but I think some of the concepts are readily adapted to today. Or could be. Alas, they rarely are. How often does a program manager estimate how much code should be required to complete a task before development begins and then try to hit that target? More often it seems we throw code together until the solution is complete, and then wonder how we end up with gigabyte sized chat clients. Without an established target, it’s hard to identify where things went wrong. Perhaps everywhere.
23. Use asserts. Asserts are not to be used in place of regular, expected error handling, but to make sure the program hasn’t gone wildly off the rails. And definitely don’t turn asserts off in production. By definition, the assertion is checking for something unexpected, so it follows that you don’t have a test case for it. The unexpected conditions will occur unexpectedly! And then you’ll be happy the assertions are still there.
24. Use exceptions sparingly.
25. Finish what you start. If a function allocates a resource, it should free the resource. Also, free resources in the opposite order that you acquire them. This makes it easy to check for correctness. My experience has been that this approach solves a lot of problems, but code doesn’t usually evolve this way naturally. It’s only after a few bugs are found and fixed that I pay attention to object lifetimes and attempt to impose some sort of order. One final tip: since you’re bound to lose track of things, keep lots of counters. When things go mysteriously wrong, some examination of the outstanding object population may provide a hint.
We’re going to string some relays and gates together in surprising ways this week. First, what happens if we take the output of a relay and connect it to the input of the same relay? We get a flippy floppy. No, actually we get an oscillator. Some people like to call the oscillator a clock.
We might also wire up some NOR gates where the output of the first leads to the second, and the output of the second leads back to the first. Now this really is a flip flop. The output will be consistently on or off, as if it remembers what’s happening. If we want the flip flop to only remember inputs at certain times, we can add a Hold That Bit input. So now we’ve got a level triggered D-type flip flop. We might also call the Hold That Bit input the Clock. And we might call the circuit a latch. Stick a bunch of these together, and we have some memory for our adding machine. We can keep inputting new numbers and adding them to the existing sum.
And then we can make our flip flop edge triggered. And then feed it back into itself yet again, giving us a frequency divider. And then we combine a bunch of those together and we have a ripple counter. All sorts of nifty circuits are possible by feeding outputs back into the input. How about an edge triggered D-type flip flop with preset and clear? Sure thing.
It’s kind of hard to summarize a chapter that consists mostly of circuit diagrams, but lots of terms and concepts introduced here are starting to sound familiar. Latches and clocks and triggers sound like things that people writing operating system device drivers that interface with hardware care about. I’ve also found it occasionally helpful to reuse circuit concepts when building software state machines.
When a program, or more precisely a component, is small enough we can understand everything it does. When it gets larger, we have to settle for understanding its interface. However, there can be two problems. First, the implementation may be incomplete or unreliable even though everything looks good from the outside. Second, we may also misunderstand the interface. The cumulative potential for problems increases with scale.