Paper Pursuit #7 - A design methodology for reliable software systems
Author: B. H. LISKOV 
Original paper: A design methodology for reliable software systems
I enjoy reading papers that talk about the process behind good software design. Such papers make you think above your everyday worries about software and provide a high-level view. As you grow in your software career, you’ll find yourself making decisions that will impact several parts of large systems, eventually affecting how individuals work with them.
This paper by Liskov provides the groundwork for designing reliable software systems by formalising the methodology for the software design process. It bases the idea of a reliable design on modularity (right abstractions) and structured programming and describes how each can be ensured while designing a complex software system.
A complex system is defined by the two characteristics mentioned in the illustration below. Historically, extensive debugging and testing have been popular methods to detect errors in a system and remove them to make the system more reliable. This works for a simple system but fails to be sufficient for a complex one.
“Program testing can be used to show the presence of bugs, but never to show their absence.” — Dijkstra
Good management techniques and design methodology are crucial to building a reliable system. Good management ensures that the system proposed by the design gets implemented correctly.
Criteria for a good design
The paper discusses two major factors that support the idea of a good design.
Levels of abstraction
Levels of Abstraction
Modularity has been a popular technique to build complex systems that are reliable. The idea is to “divide and rule” where the system is divided into smaller, independent modules that can be tested and built independently. This usually though introduces additional complexity. The paper in its later sections defines how good modularity can be achieved.
Levels of abstraction define modularity in a new way where modules are built as layers of hierarchy that follow the two conditions:
Each level has its own resources which are not accessible by other levels
Lower levels are not aware of the existence of any higher levels
Today most of the programs we write are structured programs. But it was not the case when the paper was written. A structured program is one which essentially does not have goto-like statements. Control can flow only through a set of defined methods like conditionals, iterative loops etc.
The biggest advantage of structured programs is that we can mathematically prove their correctness. This lets us reason about them more predictable than unstructured programs.
The idea of structured programming can be applied to system design. Levels of abstraction chooses the right components and then with structured programming these components are composed to form higher-level components.
Good modularity. But how?
The overall idea as mentioned in the previous section is that the system is divided into a hierarchy of levels or partitions, and a structured program defines how control flows through these levels. This requires the following to be true:
Each level is restricted by the rules of levels of abstraction and follows the rules of a structured program.
Each level communicates through explicit arguments.
Each level does only one thing (the abstraction it provides) and nothing else.
Implementing Good Modularity
Usually, the division of levels is done by inspecting the execution-time flow of the system and organising each sequential task in a component. This though creates complex data dependencies between levels and it violates the condition for good modularity. The paper then proposes that choosing the right abstractions and following a method while proceeding with the design helps.
Abstractions can be implemented in various ways depending on their type.
Resource abstraction: Abstracting a real resource by mapping functions to it. E.g. typical ORMs (object-relational mappings) abstract the underlying resource — the database. Another example is Nakadi which abstracts Kafka-like queues.
Abstract characteristics of data: Data stored in a database/anywhere is mapped to a structure more usable by external systems. E.g. any REST API that reads from a database abstracts the stored data by exposing a response structure.
reduce, the implementation is hidden in the runtime but they expose specific APIs.
Simplification via generalization: When a function is repeated in several levels, it makes sense to separate it into a common level. Lodash functions are a great example of this concept.
System maintenance and modification: Parts of the system that are prone to change can be abstracted into a separate level so that they can evolve independently. E.g. we at Zalando wanted to support our legacy Mosaic framework while we were migrating to Rendering Engine, hence we built an abstraction over Mosaic that could be used natively in the new architecture. This helped us gradually deprecate the old setup.
Proceeding with the design
A two-phased process is then suggested to implement the system design.
Phase 1: Define and identify various abstractions.
Phase 2: Investigate the practicality of abstractions and define the data connections and control flow between abstractions.
Takeaways and further readings
The paper summarizes building reliable systems using the right levels of abstraction and following a structured program approach. This methodology in combination with good management techniques can ensure system reliability.
Further related readings:
The structure of the “THE”-multiprogramming system, Edsger W. Dijkstra
Micro Frontends: Deep Dive into Rendering Engine (Part 2), Jan Brockmeyer et al.
Did you enjoy today’s issue? Consider sharing it with someone who may be interested in such content! 🧡 I write every Tuesday but next week I’ll be on a break. So I’ll see you with another paper in two weeks!