Chapter 5: Information Hiding (and Leakage)
Core Idea
Section titled “Core Idea”The most important technique for building deep modules is information hiding: each module encapsulates a few design decisions that don’t appear in its interface. Its opposite, information leakage, happens when one design decision is reflected in several modules, and a common cause is structuring code by the order in which things happen.
Frameworks Introduced
Section titled “Frameworks Introduced”- Information hiding (from David Parnas): “each module should encapsulate a few pieces of knowledge, which represent design decisions.” The knowledge lives in the implementation and not in the interface.
- What gets hidden: data structures and algorithms for a mechanism (how to store data in a B-tree, implement TCP, schedule threads, or parse JSON), lower-level details (such as page size), and higher-level assumptions (such as that most files are small).
- Why it works: it simplifies the interface (less cognitive load), and it makes evolution easier, because a change to hidden information affects only one module.
- How: when designing a module, ask what information it can hide. More hidden information should let you simplify the interface.
- Partial information hiding also helps: a feature needed by only a few users, reached through separate methods, is “mostly hidden.”
- Information leakage: “a design decision is reflected in multiple modules.” Any change to that decision then changes all of those modules.
- Leakage through an interface: if information appears in a module’s interface, it has leaked by definition.
- Back-door leakage: two classes both know a file format (one reads, one writes) without either exposing it. This is “more pernicious,” because it isn’t obvious.
- How to fix: ask “How can I reorganize these classes so that this particular piece of knowledge only affects a single class?” Either merge small, closely tied classes, or pull the knowledge into a new class, but only if that class can have a simple interface. Otherwise you have just replaced back-door leakage with interface leakage.
- Temporal decomposition: structuring a system by the time order of operations (read, then modify, then write). Both the reader and the writer must know the file format, so the knowledge leaks.
- How to avoid: “focus on the knowledge that’s needed to perform each task, not the order in which tasks occur.” Order still matters and will appear somewhere, but not in the module structure unless the stages truly use different information.
- Make classes “do the right thing” by default: defaults keep the common case simple and are a form of partial information hiding. “The best features are the ones you get without even knowing they exist.”
Key Concepts
Section titled “Key Concepts”- Private ≠ hidden: declaring a variable private doesn’t hide information if getters and setters expose its nature and use.
- Red Flag: Information Leakage: “the same knowledge is used in multiple places.”
- Red Flag: Temporal Decomposition: execution order is reflected in code structure, so the same knowledge is coded in several places.
- Red Flag: Overexposure: the API for a common feature forces users to learn about rarely used features.
- Raising the level of the interface: replace separate methods for the steps of a computation with one method that does the whole computation.
- Information hiding within a class: design each private method to encapsulate some capability, and minimize the number of places each instance variable is used.
Mental Models
Section titled “Mental Models”- “Information hiding can often be improved by making a class slightly larger.” Bring together all code for one capability, and raise the interface level.
- Treat sensitivity to leakage as a core skill. The author calls leakage “one of the most important red flags in software design.”
- Think of hiding and depth as one idea. A module that hides a lot tends to offer more functionality through a smaller interface. A module that hides little is shallow, either because it does little or because its interface is complex.
- Decompose by knowledge, not by time. List the pieces of knowledge the application needs, and give each module one or a few of them.
Anti-patterns
Section titled “Anti-patterns”- Read class + parse class: a temporal split of one capability (see the worked example).
- Returning your internal data structure:
getParams()returning the internalMapexposes the representation, makes callers do more work, and lets them corrupt internal state. - Making callers supply what the module already knows: requiring the HTTP version in a response, when it must match the request that is already passed in.
- Taking it too far: hiding information that users really need. If different uses of a module need different performance settings, expose those parameters. Better still, have the module tune itself, which removes them from the interface.
Code Examples
Section titled “Code Examples”// Shallow: exposes the internal representationpublic Map<String, String> getParams() { return this.params;}
// Deeper: hides the representation and the conversionpublic String getParameter(String name) { ... }public int getIntParameter(String name) { ... }- What it demonstrates: the second interface returns one value at a time, hides how parameters are stored, and does string-to-integer conversion for the caller. Both methods throw an exception if the parameter is missing or can’t be converted.
Worked Example
Section titled “Worked Example”Students implementing an HTTP server library (CS 190). A request (Figure 5.1) is text sent over TCP: an initial line with the method, URL, parameters, and protocol version (POST /comments/create?photo_id=246 HTTP/1.1), then header lines such as Content-Length: 40, an empty line, and a body with more parameters (comment=what+a+cute+baby%21&priority=low).
- Too many classes. One team used one class to read a request from the network into a string and another class to parse the string (“first we read the request, then we parse it”). But you can’t read a request without parsing much of it, because
Content-Lengthsets the body length. Both classes needed to understand HTTP structure, parsing code was duplicated, and callers had to call two methods in two classes in the right order. Fix: merge them into one class with one method. It hides the request format in one place and is deeper. - Good choices on parameters. Most teams hid whether a parameter came from the initial line or the body, merging both. They also hid URL encoding, returning “What a cute baby!” rather than the encoded form.
- A shallow parameter API. Most teams returned the internal
Map; the fix isgetParameterandgetIntParameter(see above). - Defaults in responses. One team made callers specify the HTTP version for each response. The library already knows it from the request, and the caller probably doesn’t, so the library should supply it, along with a default
Dateheader. Callers who need a different value can call a special method.
Key Takeaways
Section titled “Key Takeaways”- For each module, decide which design decisions it owns, and keep them out of its interface.
- When two modules share knowledge, merge them or move the knowledge into one module with a simple interface.
- Don’t split code by execution phase when the phases share knowledge.
- Don’t return internal data structures; return the values callers actually need.
- Provide defaults for anything most callers shouldn’t have to think about.
- Expose information that users really need, and nothing more.
Connects To
Section titled “Connects To”- Ch 4: information hiding is how modules become deep.
- Ch 6: general-purpose interfaces hide more information.
- Ch 8: pulling configuration parameters down into the module.
- Ch 9: when to combine or separate classes.
- Ch 13: comments for cross-module design decisions when some leakage can’t be avoided.
- Parnas, “On the Criteria to be Used in Decomposing Systems into Modules” (Communications of the ACM, December 1972 in the chapter’s footnote).