Skip to content

Chapter 6: General-Purpose Modules are Deeper

Build new modules in a “somewhat general-purpose” way: the functionality should reflect today’s needs, but the interface should not. The surprising main benefit is not future reuse but a simpler, deeper interface and better information hiding today.

  • Somewhat general-purpose: “the module’s functionality should reflect your current needs, but its interface should not.”
    • When to use: whenever you design a new module and must choose between general-purpose and special-purpose.
    • How: make the interface general enough to support multiple uses, and easy to use for today’s needs without being tied to them. “The word ‘somewhat’ is important”: don’t build something so general that it is hard to use for your current needs.
    • Why it works: general-purpose interfaces have fewer, deeper methods and keep higher-level concepts out of lower-level classes.
  • Three questions for finding the balance:
    1. “What is the simplest interface that will cover all my current needs?” Fewer methods with the same capabilities usually means more general methods, but only if each method stays simple. If you need many extra arguments to cut the method count, you may not be simplifying.
    2. “In how many situations will this method be used?” A method designed for one particular use is a red flag. Try to replace several special-purpose methods with one general-purpose method.
    3. “Is this API easy to use for my current needs?” If using the class requires a lot of extra code, the interface lacks the right functionality. This question tells you when you’ve gone too far.
  • Determine who needs to know what, and when: “When the details are important, it is better to make them explicit and as obvious as possible.” Hiding important details behind an interface creates obscurity.
  • Special-purpose interface: an interface shaped around one current use, such as a text class with a method per editor keystroke.
  • General-purpose interface: an interface defined in terms of the module’s own basic concepts (text, positions), not its callers’ features.
  • False abstraction (applied): backspace() in a text class claims to hide which characters get deleted, but UI developers need to know exactly that, so they read its code anyway.
  • Information leakage through specialization: user interface concepts such as selection and the backspace key appear inside the text class, so every new UI operation needs a new text-class method.
  • Think of the general-purpose approach as an investment (ch03) that pays back immediately in simplicity, even if the module is never reused.
  • Think of special-purpose code as belonging above general-purpose code. The text class knows text; the UI class knows keys and selections.
  • Slightly longer calling code can be better if it is more obvious. The general version of backspace is one line in the UI that shows exactly which characters are deleted.
  • One method per UI feature in a lower-level class: many shallow methods, many used in only one place, and high cognitive load for both sides.
  • Too general to be useful: a text class with only single-character insert and delete is simple and general, but callers need loops for every range operation, and it is slow for large edits.
  • Speculative generality: facilities that are never needed. Keep the functionality to today’s needs.
// Special-purpose text API (shallow, leaks UI concepts)
void backspace(Cursor cursor);
void delete(Cursor cursor);
void deleteSelection(Selection selection);
// General-purpose text API
void insert(Position position, String newText);
void delete(Position start, Position end);
Position changePosition(Position position, int numChars);
// UI code built on the general API
text.delete(cursor, text.changePosition(cursor, 1)); // delete key
text.delete(text.changePosition(cursor, -1), cursor); // backspace key
  • What it demonstrates: one general delete replaces three special-purpose methods. delete(start, end) removes characters at positions ≥ start and < end. changePosition moves forward for a positive count and backward for a negative one, skipping across lines as needed. Position replaces the UI-specific Cursor type.

The text class for a GUI editor (CS 190). Students built editors with multiple views of one file and multi-level undo and redo, plus a class that stores the file’s text.

  1. Special-purpose design. Many teams gave the text class backspace, delete, and deleteSelection. The methods were shallow and many were called from only one place. UI developers had to learn many text-class methods, and adding a UI operation meant changing the text class, so the two classes could no longer be developed independently.
  2. General-purpose design. The text class offers only insert, a range delete, and changePosition. The UI builds backspace and delete from them. The UI code is a little longer, but it is more obvious, and there is less code overall.
  3. Reuse as a bonus. A tool that replaces all occurrences of one string with another can use the general class almost unchanged. It needs only Position findNext(Position start, String string);, which an editor with search and replace would already have. The special-purpose methods would be useless to it.
  4. Applying the three questions. The simplest interface covering current needs has one deletion method, not three. backspace is used in one situation only, which is a red flag. And range operations are kept (rather than single characters) because the editor needs them to be easy and fast.
  1. Shape the interface around the module’s own concepts, and the functionality around current needs.
  2. Count how many places will call a method; a single-use method is a sign of over-specialization.
  3. Prefer fewer, more general methods, as long as each stays simple to call.
  4. Stop generalizing when callers must write a lot of extra code for today’s uses.
  5. Make important details explicit in the caller rather than hiding them behind a misleading method.
  • Ch 3: generality looks like an investment and pays off as simplicity.
  • Ch 4: general-purpose interfaces are deeper.
  • Ch 5: specialization leaks information between classes.
  • Ch 7: character-oriented interface over line-oriented storage.
  • Ch 8: why putting backspace in the text class is not “pulling complexity down.”
  • Ch 9: separating general-purpose from special-purpose code.