第九章:Better Together Or Better Apart?

one of the most fundamental questions in software design is this: given two pieces of functionality, should they be implemented together in the same place, or should their implementations be separated?
This question applies at all levels in a system, such as functions, methods, classes, and services.
when deciding whether to combine or separate, the goal is to reduce the complexity of the system as a whole and improve its modularity.

It might appear that the best way to achieve this goal is to divide the system into a large number of small components: the smaller the components, the simpler each individual component is likely to be.

subdividing creates addition complexity

However, the act of subdividing creates additional complexity that was not present before subdivision:

  • some complexity comes just from the number of components
    the more components, the harder to keep track of them all and the harder to find a desired component within the large collection.
    Subdivision usually results in more interfaces, and every new interface adds complexity.
  • Subdivision can result in additional code to manage the components.
    one piece of code that used a single object before subdivision might now have to manage multiple objects.
  • Subdivision creates separation
    Separation makes it harder for developers to see the components at the same time, or event to be aware of their existence
    components are truly independent, then separation is good: focus on a single component
    there are dependenciees between the components, then separation is bad: will flipping back and forth between the components. Even worse, not be aware of the dependencies.
  • Subdivision can result in duplication: code that was present in a single instance before subdivision may need to be present in each of the subdivided components.

Bringing pieces of code together is most beneficial if they are closely related
If the pieces are unrelated, they are probably better off apart

a few indication that two pieces of code are related

  • They share information. ex. depend on the syntax of a particular type of document.
  • They are used together. counter-ex. a disk block cache will almost always involve a hash table, but hash tables can be used in many situations that don't involve block caches, these modules should be separate.
  • They overlap conceptually.
    ex. searching for a substring and case conversion both fall under the category of string manipulation
    ex. flow control and reliable delivery both fall under the category of network communication.
  • It is hard to understand one of the pieces of code without looking at the other.

Bring together if information is shared

implementing an HTTP service. the project used two different methods in different classes to read in and parse HTTP requests.
The first method read the text of an incoming request from a network socket and placed it in a string object
The second method parsed the string to extract the various components of the request.
With this decomposition, both of the methods ended up with considerable knowledge of the format of HTTP requests.
Read method couldn't identify the end of ther request without doing most of the work of parsing it. Because of this shared information, it is better to both read and parse the requests in the same place; when the two classes were combined into one, the code got shorter and simpler.


Bring together to eliminate duplication

If you find the same pattern of code repeated over and over, see if you can reorginaze the code to eliminate the repetition.

One approach is to factor the repeated code out into a separate method and replace the repeated code snippets with calls to the method.
This approach is most effective if the repeated code snippet is long and the replacement method has a simple signature.


Another way to eliminate duplication is to refactor the code so that the snippet in question only needs to be executed in on place


Separate general-purpose and special-purpose code

If a module contains a mechanism that an be used for several different purposes, then it should provide just that one general-purpose mechanism.
It should not include code that specializes the mechanism for a particular use, nor should it contain other general-purpose mechanisms.
Special-purpose code associated with a general-purpose mechanism should normally go in a different module (typlically one associated with the particular purpose).


If the same piece of code (or code that is almost the same) appears over and over again, that's a red flag that you haven't found the right abstractions.


In general, the lower layers of a system tend to be more general-purpose and the upper layers more special-purpose.
The way to separate special-purpose code from general-purpose code is to pull the special-purpose code upwards, into the higher layers, leaving the lower layers general-purpose.
When you encounter a class that includes both general-purpose and special-purpose features, and the other layered on top of it to provide the special-purpose features.

encounter: 偶遇

Example: insertion cursor and selection


relevant: 相关的


It provided no benefit for higher-level code, since the high-level code still needed to be aware of the selection and cursor as distinct entities, and it manipulated them separatly

In this case, the selection and cursor were not closely enough related to combine them.
The cursor implementation also got simpler becouse the cursor position was represented directly, rather than indirectly through a selection and a boolean.
Instead, a new Position class was introduced to represent a location in the file.
The selection was represented with two Positions and the cursor with one.

Red Flag: Special-General Mixture

This red flag ovvures when a general-purpose mechanism also contains code specialized for a particular use of that mechanism. This makes the mechanism more complicated and creates information leakage between the mechanism and the particular use case: future modifications to the use case are likely to require changes to the underlying mechanism as well.

Example: separate class for logging




Example: editor undo mechanism

In the GUI editor project, one of the requirements was to support multi-level undo/redo, not just for changes to the text ifself, but also for changes in the selection, insertion cursor, and view.

Some of the student projects implemented the entire undo mechanism as part of the text class. The text class maintained a list of all the undoable changes.
This approah resulted in an awkward set of features in the text class.

The core of undo/redo consists of a general-purpose mechanism for managing a list of actions that have been executed and stepping through them during undo and redo operations.


Spliting and joining methods

Long methods tend to be more difficult to understand than shorter ones, so many people argue that length alone is a good justification for breaking up a method.
Howeverm length by itself is rarely a good reason for splitting up a method.


In general, developers tend to break up methods too much.
Splitting up a method introduces additional interfaces, which add to complexity.
It also separates the pieces of the orginal method, which makes the code harder to read if the pieces are actually related.


You shouldn't break up a method unless it makes the overall system simpler;


Spliting up a method only makes sense if it results in cleaner abstractions, overall.

best way

The best way is by factoring out a subtask into a separate method, as (b)
This form of subdivision makes sense if there is a subtask that is cleanly separable from the rest of the original method.
the child method is relatively general-purpose: it could conceivably be used by other methods besides the parent.
If you make a split of this form and then find yourself flipping back and forth between the parent and child to understand how they work together, that is a red flag indecating that the split was probably a bad idea.

subdivision 细分
conceivably: 可想象地,可理解地

second way

The second way to break up a method is to split it into two separate methods, each visible to callers of the original mthod, as (c)
This make sendse if the original method had an overly complex interface because it tried to do multiple things that were not closely related.
If you make a split like this , the interface for each of the resulting methods should be simpler than the interface of the original method.


Ideally, most callers should only need to invoke one of the two new mthods; if callers must invoke both of the new methods, then that adds complexity, which makes it less likely that the split is a good idea.
The new methods will be more focused in what they do. It is a good sign if the new methods are more general-purpose than the origin method


Splits of the form shown in (c) don't make sense very often, because they result in callers having to deal with multiple methods instead of one.

avoid way

If the caller has to invoke each of the separate methods, passing state back and forth between them, then spliting is not a good idea.



Methods containing handreds of lines of code are fine if they have a simple signature and are easy to read. These methods are deep (lots of functionality, simple interface), which is good.
If the blocks have complext interactions, it's even more important to keep them together so readers can see all of the code at once; if each block is in a separate method, readers will have to flip back and forth between these spread-out methods in order to understand how they work together.


When designing methods, the most important goal is to provide clean and simple abstractions.
Each method should do one thing and do it completely.
The method should have a clean and simple interface, so that users don't need to have much information in their heads in order to use it correctly
The method should be deep: its interafce should be much simpler than its implementation.
If a method has all of these properties, then it probably doesn't matter whether it is long or not.


There are also situations where a system can be make simpler by joining methos together.

  • joining methods might replace two shallow methods with one deeper method;
  • it might eliminate duplication of code;
  • it might eliminate dependencies between the original methods, or intermediate data structures
  • it might result in better encapsulation, so that knowledge that was previously present in multiple palces is now isolated in a single palce;
  • or it might result in a simpler interface
intermediate: 居中的,中级的中间层的
encapsulation: 封装性
isolated: 独立的

Red Flag: Conjoined Methods

If two pieces of code are physically separated, but each can only be understood by looking at the other, that is a red flag.
If you can't understand the implementation of one method without also unserstanding the implementation of another, that's a red flag.


The decision to split or join medules should be based on complexity.
Pick the structure that results in the best information hiding, the fewest dependencies, and the deepest interfaces.