Pasta
Most software abstractions are deliberately opaque. Let’s consider making them, if not transparent, at least translucent.
Spaghetti
The term “computer program” is misleading, because it implies order. Computer programs are lists of instructions—things for the computer to do—that aren’t necessarily executed in list order. Computers support “jump” (or “branch” or “go to”) instructions that transfer control to different parts of the program, sometimes depending on factors that can’t be known in advance. Programs that are hard to read because of all the jumping around are called “spaghetti code.”
You still see goto
in low-level C code sometimes for resource cleanup. For example, a function that tries to copy a file might do the following:
Initialize a “success” variable with the value false.
Try to open the old file for reading.
If (2) failed, go to (10).
Try to open the new file for writing.
If (4) failed, go to (9).
Try reading the contents of the old file, and writing them to the new file.
If (6) succeeded, assign the success variable the value “true.”
Close the new file.
Close the old file.
Return the success variable’s value to the caller.
You’ll also see a glorified version of this in garbage collected languages, using defer
statements or with
or try
blocks instead of goto
. To do any better than per-call-site resource management, you need either C++/Rust style destructors, or a framework that supports lifecycle callbacks (methods or hooks).
Since the 1950s (and arguably culminating with Dijkstra’s 1968 opinion piece Go To Statement Considered Harmful), goto
statements have been considered gauche. In polite company, jumps are used only as part of common patterns like loops and function calls, collectively call structured programming.
When reading well-structured code, you can often skip over details. For example, if you see a function call like factorial(5), you probably don’t have to look at the implementation of the factorial function, because you know what it’s meant to do. That kind of abstraction is critically important for software (and development teams) to scale, because it’s not practical for someone reading code to have to follow all the jumps. There’s an equally important but underappreciated caveat to this kind of structured programming though: Abstraction should never prevent the reader from following the jumps if they want to.
It’s in vogue now to use Dependency Injection (DI), message buses/brokers/queues, and other heavyweight techniques to decouple various parts of complex systems. Tragically, most of these systems throw the baby out with the bathwater: They reason that because you ideally shouldn’t have to look past an abstraction, you should be actively prevented from doing so; or at least, supporting removal of the abstraction is not seen as a priority. Such thinking ignores the fact that abstractions are inherently imperfect. We won’t belabor that point here, because Joel Spolsky has already done a great job coining the The Law of Leaky Abstractions.
Lasagna
Sometimes you see software structured into layers, or tiers if they’re separate programs (such as a database, microservice, and web-based user interface). This kind of structure is occasionally called lasagna.
When you see a Data Access Object (DAO) or other layer-oriented abstraction, make sure you can understand how each layer is actually implemented. At least once, try following a request all the way down the call stack, and the response all the way back up. If you are building such a system, be as transparent (or translucent) as you can about it, so that your layers are easy to reason about when debugging, or assessing cost or performance.
Ravioli
Instead of (or in addition to) layers, software is sometimes structured as bundles of state called objects. These are objects in the sense of Object Oriented Programming (OOP), and while they are generally implemented by objects in the sense we discussed in Variables, they’re a more abstract idea.
The same principle applies to objects (in the OOP sense) as to layers: Don’t presume that an abstract interface is the only thing your clients should have access to. If you can reasonably let them know how that interface is implemented, you should do so. If you’re using someone else’s objects (or classes), take a peek at the source code (if available) and get a sense of how they actually work. Incidentally: Plain Old Data (POD), as opposed to fancy objects with properties and methods and access control, are underrated.
There’s a fallacy gaining traction these days, along the lines of: “Never expose any implementation details, because if you do then someone will come to rely on them, and then you’ll never be able to change your implementation.” All caller/callee relationships are subject to change. Being a service provider doesn’t make you a serf. Your clients are not your enemies. (If your clients are actively hostile, drop them.) The best way to provide value is to clearly distinguish aspects of your system that will remain stable from those that may not, letting your customers lift the hood and see how your machinery works. The possibility that they’ll somehow void the warranty isn’t a good reason to keep them in the dark.