Chapter 9: Stratified design: Part 2
The chapter introduces the next part of stratified design, previewing patterns and their relevance to maintenance, testing, and reuse.
After this chapter you will be able to
- Abstraction barrier purpose. Explain how an abstraction barrier provides an interface that hides an important implementation detail, allowing code and thought at a higher level.
- Abstraction barriers and higher-level thinking. Explain how a layer of functions hides its implementation and lets programmers focus on a higher-level problem, such as marketing campaigns rather than loops and arrays.
- Symmetrical independence across abstraction barriers. Explain how an abstraction barrier lets code on both sides ignore the other side’s details, and why changing a shopping cart’s underlying data structure need not change the marketing team’s code.
- Shopping cart data structure swap. Explain why the shopping cart might switch from an array to a JavaScript object and which functions would need to change.
- Shopping cart data structure. Explain why a JavaScript object is a better fit than an array for this shopping cart.
- Cart abstraction barrier. Explain how a layer of cart functions let the team replace an array with an object without changing code above the layer.
- Implementation flexibility and its limits. Explain how an abstraction barrier can let an implementation change later, and why adding one for a merely possible change may not be worth the code.
- Clarity through hidden details. Explain how hiding the right details makes code easier to write and read and helps programmers focus on the current problem.
- Details hidden across the barrier. Explain what code above and code at or below an abstraction barrier can ignore, using the shopping-cart example.
- Strategic use of abstraction barriers. Justify when to use an abstraction barrier rather than creating one merely in anticipation of future change.
- Straightforward implementation as a design signal. Explain why the refactored functions being one-liners is a good sign without treating line count as the measure of good design.
- Minimal interface placement principle. Explain why the minimal interface pattern favors implementing new features at higher levels instead of expanding lower-level interfaces, using the watch-discount campaign as an example.
- Logging inside cart calculation. Explain the consequences of calling logAddToCart() from add_item(), including action spreading and misleading log records.
- User intent logging location. Justify calling logAddToCart() from the add_item_to_cart() click handler in this design.
- Minimal interface and function placement. Explain how to keep an abstraction barrier complete without adding unnecessary functions, and describe when a new function should be implemented above a layer instead.
- Comfort as a design guide. Explain how comfort guides when a team should stop applying stratified-design patterns and when it should start again.
- Straightforward implementation detail. Explain why a function’s body should solve the problem stated by its signature at the right level of detail.
- Abstraction barrier purpose. Explain what an abstraction barrier hides and why that helps programmers work at a higher level.
- Minimal business concept interface. Describe how the interface to an important business concept should develop as a system evolves.
- Comfortable layer investment. Justify when to invest in layers and when to leave a comfortable design alone.
- Call graph structure and qualities. Explain what a call graph shows and why its structure can be useful for thinking about maintainability, testability, and reusability. Distinguish these nonfunctional requirements from functional correctness without claiming that this section establishes which graph positions are best.
- Change cost across layers. Explain why code near the top of a call graph is easier to change than code lower down, and how that affects where frequently changing requirements should go.
- Testing priority by layer. Explain why, when testing everything is impractical, testing code at the bottom of the design graph offers greater long-term value than testing code at the top.
- Reuse in lower layers. Explain why functions in lower layers tend to be more reusable and how the amount of code beneath a function relates to its reusability.
- Changeability near graph top. Explain why a function with fewer functions above it in the call graph is easier to change, and why frequently changing code belongs near the top.
- Test value near graph bottom. Explain why tests for functions lower in the call graph are more valuable and where to concentrate testing.
- Reuse through lower layers. Explain how the number of functions beneath a function relates to its reusability and why extracting functions into lower layers can help.
- Layers of abstraction. Explain that stratified design organizes functions into layers, with each function implemented using functions in lower layers, and that the aim is a system comfortable for the business’s needs.
- What layer structure reveals. Describe how the layer structure helps show which code is more testable, changeable, and reusable.
What it builds on
- Pattern 1: Straightforward implementations (chapter 8) · The earlier treatment of straightforward implementations
- What is stratified design? (chapter 8) · The idea of stratified design as layers
- Actions spread through code (chapter 3) · Explains how actions spread through callers.
Section by section
Introduction
The chapter introduces the next part of stratified design, previewing patterns and their relevance to maintenance, testing, and reuse.
9.1 Patterns of stratified design
This section summarizes four patterns of stratified design and the purpose of each.
- Straightforward implementation detail: Explain that a function’s body should solve the problem in its signature at the right level of detail, rather than exposing too much detail.
Why it matters: This gives you a way to spot when an implementation is harder to read than it needs to be. - Abstraction barrier purpose: Explain how an abstraction barrier provides an interface that hides an important implementation detail, allowing code and thought at a higher level.
Why it matters: Hiding implementation details frees attention for the higher-level problem. - Minimal interface principle: Describe the aim of a minimal interface for an important business concept: a small, powerful set of operations on which other operations are based.
Why it matters: A small set of foundational operations keeps an interface focused as the system evolves. - Comfortable layer investment: Justify adding layers when they help programmers deliver software faster and with higher quality, rather than adding layers for their own sake.
Why it matters: Layers are worthwhile when they improve the work, not simply because more abstraction is possible.
Key terms: straightforward implementation, code smell, abstraction barrier, interface, implementation detail, minimal interface, business concepts, operations, comfortable layers, stratified design
9.2 Pattern 2: Abstraction barrier
Introduces abstraction barriers as the second stratified-design pattern and previews their role in delegating responsibilities between teams.
9.3 Abstraction barriers hide implementations
An abstraction barrier hides implementation details behind functions so programmers can work at a higher level.
- Abstraction barriers and higher-level thinking: Explain how a layer of functions hides its implementation and lets programmers focus on a higher-level problem, such as marketing campaigns rather than loops and arrays.
Why it matters: It lets you work with concepts relevant to the task without keeping their implementation details in mind.
Key terms: abstraction barrier, implementation, higher level
9.4 Ignoring details is symmetrical
This section explains how an abstraction barrier lets the teams on either side ignore each other’s implementation details and work largely independently.
- Symmetrical independence across abstraction barriers: Explain how an abstraction barrier lets code on both sides ignore the other side’s details, and why changing a shopping cart’s underlying data structure need not change the marketing team’s code.
Why it matters: This separation lets teams change their own code without unnecessarily disrupting each other.
Key terms: abstraction barrier, implementation, shopping cart
9.5 Swapping the shopping cart’s data structure
The section shows how an abstraction barrier confines a shopping cart data-structure change to one layer.
- Shopping cart data structure swap: Explain why the shopping cart might switch from an array to a JavaScript object and which functions would need to change.
Why it matters: A clean interface helps contain a data-structure change, but it does not excuse poor performance.
Key terms: linear search, JavaScript object, hash map, abstraction barrier
9.6 Re-implementing the shopping cart as an object
Replacing the shopping cart’s array with a JavaScript object makes additions and removals more appropriate and the code cleaner without changing marketing’s code.
- Shopping cart data structure: Explain why a JavaScript object is a better fit than an array for this shopping cart.
Why it matters: Choosing a data structure that fits the operations can simplify code as well as improve efficiency. - Unchanged marketing code: Describe what happens to the marketing department’s code when the shopping cart is re-implemented as an object.
Why it matters: A change inside the cart need not force changes to code that uses it.
Key terms: JavaScript object, array, random additions and removals, straightforward, marketing department’s code, Cart as array, Cart as object
9.7 The abstraction barrier lets us ignore details
The section explains how an abstraction barrier lets code use shopping carts without depending on their underlying data structure.
- Cart abstraction barrier: Explain how a layer of cart functions let the team replace an array with an object without changing code above the layer.
Why it matters: It explains why a change to the cart’s storage required changes to only a handful of functions. - Incomplete barrier and repair: Describe how a caller can violate the cart’s abstraction barrier and how to complete it.
Why it matters: Spotting a direct dependency shows where the barrier needs another function.
Key terms: abstraction barrier, layer, data structure, incomplete abstraction barrier, splice()
9.8 When to use (and when not to use!) abstraction barriers
Explains when abstraction barriers help and why adding them for hypothetical future changes can be wasteful.
- Implementation flexibility and its limits: Explain how an abstraction barrier can let an implementation change later, and why adding one for a merely possible change may not be worth the code.
Why it matters: This helps you weigh useful flexibility against code written for a change that may never happen. - Clarity through hidden details: Explain how hiding the right details makes code easier to write and read and helps programmers focus on the current problem.
Why it matters: The main payoff is being able to work on a problem without holding every implementation detail in mind. - Independent work across teams: Describe how an abstraction barrier can reduce coordination when different teams work on either side of it.
Why it matters: A useful boundary can save teams from unnecessary back-and-forth.
Key terms: abstraction barrier, layer of indirection, implementation, details, problem at hand, coordination, development team, marketing
9.9 Pattern 2 Review: Abstraction barrier
Reviews how an abstraction barrier lets code on each side ignore different details and why it should be used strategically.
- Details hidden across the barrier: Explain what code above and code at or below an abstraction barrier can ignore, using the shopping-cart example.
Why it matters: This gives each side fewer details to keep in mind when working with the cart code. - Strategic use of abstraction barriers: Justify when to use an abstraction barrier rather than creating one merely in anticipation of future change.
Why it matters: It helps you choose a barrier for a present communication or clarity problem, not just a hypothetical change.
Key terms: abstraction barrier, code above the barrier, code at or below the barrier, abstraction barriers, ignoring details, inter-team communication
9.10 Our code is more straightforward
The section revisits straightforward implementation, using the shopping-cart refactor to show why one-line functions can signal a well-chosen level of abstraction.
- Straightforward implementation as a design signal: Explain why the refactored functions being one-liners is a good sign without treating line count as the measure of good design.
Why it matters: It helps you judge a design by the clarity of its abstraction rather than by how short its functions are.
Key terms: straightforward implementation, level of generality and detail, one-liners
9.11 Pattern 3: Minimal interface
This section uses two shopping-cart features to show why minimal interface favors adding new behavior above a clean lower-level interface.
- Minimal interface placement principle: Explain why the minimal interface pattern favors implementing new features at higher levels instead of expanding lower-level interfaces, using the watch-discount campaign as an example.
Why it matters: A small interface lets teams add features without taking on unnecessary shared code and coordination. - Logging inside cart calculation: Explain the consequences of calling logAddToCart() from add_item(), including action spreading and misleading log records.
Why it matters: A convenient-looking logging location can change how callers behave and produce records of events that never happened. - User intent logging location: Justify calling logAddToCart() from the add_item_to_cart() click handler in this design.
Why it matters: Placing the log at the user interaction records actual adds without changing the cart’s clean interface.
Key terms: minimal interface, abstraction barrier, contract, higher levels, logAddToCart(), add_item(), action, calculation, spreading rule, update_shipping_icons()
9.12 Pattern 3 Review: Minimal interface
Reviews the minimal interface pattern by explaining why layers should expose only the functions they need and where new functions should go.
- Minimal interface and function placement: Explain how to keep an abstraction barrier complete without adding unnecessary functions, and describe when a new function should be implemented above a layer instead.
Why it matters: It helps you add useful behavior without needlessly expanding a lower-level interface. - Costs of a larger barrier: Explain why adding unnecessary functions to an abstraction barrier can make the code harder to maintain.
Why it matters: A smaller barrier reduces the amount of low-level code and interface that people must maintain. - Ideal of a lasting layer: Describe how the minimal interface pattern applies beyond abstraction barriers and what its ideal looks like.
Why it matters: It gives you a way to judge whether a layer’s functions serve its purpose without unnecessary additions.
Key terms: abstraction barrier, interface, minimal interface, layer, implementation, low-level code, complete, minimal, and timeless, layers, call graph
9.13 Pattern 4: Comfortable layers
Introduces comfortable layers as a practical way to decide when to stop refining a design and when to improve it again.
- Comfort as a design guide: Explain how comfort guides when a team should stop applying stratified-design patterns and when it should start again.
Why it matters: It gives your team a practical way to balance design work with delivering features. - Limits of tall abstractions: Explain why building ever-higher layers is not always a practical goal, despite the value of good abstraction.
Why it matters: It explains why a usable design can be better than spending too long pursuing an ideal one.
Key terms: comfortable layers, comfort pattern, stratified design, abstraction barrier, layers of abstraction, robust layers
9.14 Patterns of stratified design
This section recaps the four patterns of stratified design and what each contributes to a useful layer structure.
- Straightforward implementation detail: Explain why a function’s body should solve the problem stated by its signature at the right level of detail.
Why it matters: It gives you a way to notice when an implementation exposes more detail than its purpose calls for. - Abstraction barrier purpose: Explain what an abstraction barrier hides and why that helps programmers work at a higher level.
Why it matters: Hiding implementation details leaves more attention for the higher-level problem. - Minimal business concept interface: Describe how the interface to an important business concept should develop as a system evolves.
Why it matters: A small set of powerful operations keeps an important interface focused as the system grows. - Comfortable layer investment: Justify when to invest in layers and when to leave a comfortable design alone.
Why it matters: The point of layering is to help deliver software, not to maximize the number of abstractions.
Key terms: straightforward implementation, right level of detail, code smell, abstraction barrier, interface, implementation detail, minimal interface, business concepts, operations, comfort
9.15 What does the graph show us about our code?
This section explains that the structure of a call graph, even without function names, can reveal important qualities of code beyond whether it works correctly.
- Call graph structure and qualities: Explain what a call graph shows and why its structure can be useful for thinking about maintainability, testability, and reusability. Distinguish these nonfunctional requirements from functional correctness without claiming that this section establishes which graph positions are best.
Why it matters: It gives you a way to consider how code is organized, not only whether it returns the right answer.
Key terms: call graph, functional requirements, nonfunctional requirements, NFRs, maintainability, testability
9.16 Code at the top of the graph is easier to change
The section explains why code near the top of a call graph is easier to change and how that guides the placement of frequently changing code.
- Change cost across layers: Explain why code near the top of a call graph is easier to change than code lower down, and how that affects where frequently changing requirements should go.
Why it matters: Placing changing rules above stable functions reduces the amount of code a change might affect.
Key terms: call graph, layers of change, business rules, copy-on-write functions
9.17 Testing code at the bottom is more important
Explains why tests for stable code in lower layers tend to pay off more over time than tests for frequently changing code in higher layers.
- Testing priority by layer: Explain why, when testing everything is impractical, testing code at the bottom of the design graph offers greater long-term value than testing code at the top.
Why it matters: It helps you prioritize testing effort when you cannot test everything.
Key terms: stratified design, layers of testability, code at the bottom
9.18 Code at the bottom is more reusable
This section explains why lower layers in stratified design offer more opportunities for code reuse.
- Reuse in lower layers: Explain why functions in lower layers tend to be more reusable and how the amount of code beneath a function relates to its reusability.
Why it matters: Reusable code saves the work of writing, testing, and changing the same code twice.
Key terms: stratified design, lower layers, reuse
9.19 Summary: What the graph shows us about our code
Summarizes three rules of thumb for using a call graph to judge where code is easiest to change, most valuable to test, and most reusable.
- Changeability near graph top: Explain why a function with fewer functions above it in the call graph is easier to change, and why frequently changing code belongs near the top.
Why it matters: The graph can help you choose a layer for code you expect to change often. - Test value near graph bottom: Explain why tests for functions lower in the call graph are more valuable and where to concentrate testing.
Why it matters: The graph helps you prioritize tests where more code relies on the result. - Reuse through lower layers: Explain how the number of functions beneath a function relates to its reusability and why extracting functions into lower layers can help.
Why it matters: The graph can guide where to place a function you want to reuse.
Key terms: call graph, maintainability, top of the graph, testability, bottom of the graph, reusability, lower layers
9.20 Conclusion
The conclusion recaps how stratified design organizes functions into layers and helps reveal where code is easier to test, change, and reuse.
- Layers of abstraction: Explain that stratified design organizes functions into layers, with each function implemented using functions in lower layers, and that the aim is a system comfortable for the business’s needs.
Why it matters: The layers give you a way to organize functions around the work the business needs done. - What layer structure reveals: Describe how the layer structure helps show which code is more testable, changeable, and reusable.
Why it matters: Looking at the layers can help you judge where code is easier to work with.
Key terms: stratified design, layers of abstraction, layer structure, testable, changeable, reusable
9.21 Summary
9.22 Up next…
Closes the first part of the book and previews the next chapter’s move toward using code like data.
Where this is used later
- Abstraction barriers on deeply nested data (chapter 14) · Applies abstraction barriers to deeply nested data.
- Facilitating change and reuse (chapter 18) · Supports the later discussion of how architecture facilitates reuse.
- Review: Stratified design (chapter 18) · The later review draws on the idea of functions organized in layers of abstraction.
Concepts explained in this chapter
- minimal interfaceA stratified-design pattern in which important business concepts are represented by a small, powerful set of operations from which other operations can be constructed.
- action spreading ruleA rule stating that action status propagates upward through caller paths in a function call graph.
- comfortable layerA stratified-design pattern in which abstraction layers are added and shaped to support programmer productivity and software quality rather than abstraction for its own sake.
- stratified designAn architectural pattern that organizes software into layers ordered from relatively stable foundations to more frequently changing elements.
- straightforward implementationA software implementation pattern that expresses behavior with operations at similar levels of abstraction while omitting irrelevant lower-level details.
- call graphA graph representation in which program entities are connected according to which functions or procedures call which others.
- reuseThe practice of using existing code in multiple places or contexts instead of duplicating its implementation.
- actionAn action is a computation whose observable result depends on when or how it is performed and may involve effects beyond producing a value.
- dataAn inert representation of information that requires interpretation to acquire meaning and can be stored or transmitted.
- loopAn iteration construct that repeatedly executes a block while progressing through a sequence or range of values.
- mathematical calculationA calculation is a category of code that deterministically computes a result from its inputs without directly interacting with the outside world.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
