Book cover

Buy the e-book on Leanpub

To report errors or typos, use this form.

Home | Dark Mode | Cite

Software Engineering: A Modern Approach

Marco Tulio Valente

1 Modules Should Be Deep

1.1 Introduction

John Ousterhout is a professor at Stanford University and the creator of the Tcl/Tk scripting language. In 2018, he published a book on software design principles.

One of his main recommendations—and probably one of the most original compared to other literature on software design—is the following: the modules of a system should be deep.

Before explaining the concept of deep, let’s first define a module. In software design, a module is any code element that has both an implementation and an interface.

Modules = Interface + Implementation

Modules can vary in size and include functions, classes, and packages. For functions, the interface is defined by their signature.

1.2 Deep Modules

Returning to Ousterhout’s advice, a module is considered deep when its implementation is significantly more complex than its interface. In other words, modules should offer their clients a simple interface while providing a complex implementation behind it.

On the other hand, a shallow module has an interface that is nearly as complex as its implementation. To better understand this concept, consider the following illustrations:

Deep modules vs shallow modules

As an example of a deep module, the author mentions the Unix file system, whose interface consists of only five functions:

int open(const char* path, int flags, mode_t permissions);
ssize_t read(int fd, void* buffer, size_t count);
ssize_t write(int fd, const void* buffer, size_t count);
off_t lseek(int fd, off_t offset, int referencePosition);
int close(int fd);

The implementation of a file system in any operating system is quite complex, as it involves dealing with disk devices, maintaining data structures to store files, managing permissions, supporting concurrent access to files, handling caches, and other intricate tasks. However, in the Unix case, this complexity is hidden behind a very simple interface (the five functions shown above). Importantly, Ousterhout notes that the design of Unix file systems has radically evolved over the years, but this evolution has been transparent to users because the signatures of the five functions have remained unchanged.

The author mentions a second example of a deep module: garbage collectors. Despite their complexity, garbage collection algorithms are encapsulated in modules that have virtually no visible interface to most programmers.

In my lectures, I often use an alternative metaphor proposed in a book by Prof. Bertrand Meyer. He suggests that modules should be like icebergs, with a small tip (the interface) and a large base (the implementation), which is submerged and invisible to external observers. The figure below, used in Meyer’s book, illustrates this concept:

Modules should be like icebergs

1.3 Shallow Modules

On the other hand, a shallow module has an interface that is relatively complex compared to its implementation. As an extreme example, the author presents the following function:

void addNullValueForAttribute(String attribute) { // interface
  data.put(attribute, null);    // implementation
}

In this case, it is simpler and easier to directly call:

data.put(attribute, null);

than to implement a function solely for this purpose. The function addNullValueForAttribute adds complexity to the system without providing significant benefits.

Exercises

1. Give two examples of deep modules. To facilitate your answer, consider packages or libraries in your preferred programming language.

2. Consider the following statement: deep modules necessarily imply large functions or classes with many lines of code. Is this statement true or false? Justify and discuss your answer.

3. Suppose the following method of the String class in Java:

public boolean isEmpty() {
  return value.length == 0;
}

Is this method shallow? Justify and discuss.

Suggestion: To answer the question think about the code that calls isEmpty(). For instance, would you prefer the following code

if (myString.isEmpty()) {
    ...
}

over code like this:

if (myString.value.length() == 0) {
    ...
}

4. Consider the following statement: every shallow module is small, but not every small module is shallow. Is this statement true or false? Justify and discuss. (Note: A small module is one that has a few lines of code.)

5. Consider the following statement taken from Martin Fowler’s Refactoring book (page 90):

Small methods really work only when you have good names, so you need to pay attention to naming. People sometimes ask me what length I look for in a method. To me length is not the issue. The key is the semantic distance between the method name and the method body.

Explain the concept of semantic distance between the method name and the method body, as mentioned in this quotation.