10
• How hard is it to write your program so that the language guarantees a safe, bulletproof abstract
state, in which a variable always has the expected type and only an explicit write can change
its value? Usually this means strong typing and garbage collection. Java is safe in this sense,
C++ is not (unless you hide unsafe features behind a safe abstraction),
R65
and JavaScript is in
between. If the abstract state isn’t bulletproof, debugging is much harder.
• Is the language well matched to your problem domain? Is it easy to say the things that you say
frequently? Is it possible to say all the things that you need to say?
• What static checking does the compiler do? A bug found at compile time is much easier to fix.
• How hard is it to make your program efficient enough, and to measure how it uses resources?
2.4 Modules and interfaces—Keep it clean. Keep basic interfaces stable.
The only known way to build a large system is to reinforce abstraction with divide and conquer:
break the system down into independent abstractions called modules. I’ll call the running code of
a module a service; sometimes people call it an object. The spec for a module does two things:
− it simplifies the client’s life by hiding the complexity of the code (see above), and
− it decouples the client from the code, so that the two can evolve independently.
Thus many people can work on the system productively in parallel without needing to talk to each
other. Since a spec embodies assumptions that are shared by more than one part of a system, and
sometimes by a great many parts, changing it is costly.
It’s common to call the spec of a module its interface, and I’ll do this too. Unfortunately, in
common usage an interface is a very incomplete spec that a compiler or loader can process, giving
just the data types and the names and (if you’re lucky) the parameters of the operations, rather than
what the actions do with the state. Even a good description of the state is often missing.
A really successful interface is like an hourglass: the spec is the narrow neck, with many clients
above and many codes below; it can live for decades. Examples: CPU ISAs (instruction set archi-
tectures such as x86 and ARM), file systems (Posix), reliable messages (TCP), names for Internet
services (DNS), web pages (HTTP and HTML). Ousterhout’s book on software design
R53
gives
many smaller examples, emphasizing how important it is to make the spec much smaller and sim-
pler than the code.
A module boundary doesn’t just decouple its code from the clients; it can decouple its execu-
tion and resource consumption as well. If the interface is asynchronous neither side waits for the
other, so the service can keep running no matter what the clients are doing, and vice versa. And it
can manage the way it consumes storage and other resources independently of its clients. Thus the
service is an autonomous agent. How does this show up in the spec? A complete spec doesn’t just
say enough about the service’s internal state to say what results it returns; the spec also describes
how it consumes any resources it shares with its clients. An autonomous service doesn’t share
resources, so its spec is simpler and a system that uses it is more dependable and easier to change.
Distributed transactions are an interesting example.