Skip to content

Chapter 16: Modifying Existing Code

A mature system’s design comes more from its changes than from its first design, so stay strategic when modifying code. After each change, the system should look as if it had been designed with that change in mind. Keep comments current by placing them near the code, avoiding duplication, and checking diffs.

  • Stay strategic when modifying code:
    • The usual tactical mindset is “what is the smallest possible change I can make that does what I need?” Each minimal change adds special cases and dependencies, and the design gets a little worse every time.
    • Goal: “when you have finished with each change, the system will have the structure it would have had if you had designed it from the start with that change in mind.”
    • How: ask whether the current design is still the best one given the change. If not, refactor. Even when the change doesn’t require it, fix design imperfections you see. “If you’re not making the design better, you are probably making it worse.”
    • Under real constraints (a three-month refactoring versus a two-hour fix, a tight deadline, incompatibilities for other teams), ask “Is this the best I can possibly do to create a clean system design, given my current constraints?” Look for an option almost as clean that takes a couple of days, or get your boss to schedule the refactoring after the deadline. Every organization should spend a small fraction of its effort on cleanup.
  • Four techniques for maintaining comments:
    1. Keep the comments near the code. Put a method’s interface comment next to its body in the code file, not in a C/C++ header, and rely on Doxygen, Javadoc, or the IDE to present it to users. Push implementation comments down to the narrowest scope that covers the code they describe, with one comment per phase. An overview of the strategy at the top of a method is fine.
    2. Comments belong in the code, not the commit log. If future developers will need the information (such as a subtle problem that motivated a change), put it in the code, or someone may undo the change and recreate the bug.
    3. Avoid duplication. Document each design decision once, in the most obvious place, such as next to a variable’s declaration. Elsewhere, add a pointer: “See the comment in xyz for an explanation of the code below.” Use a designNotes file if there is no obvious place. Don’t re-document a called method before its call site, and reference external documentation (an HTTP spec URL, a user manual) instead of copying it.
    4. Check the diffs. Before committing, scan all of the changes and make sure the documentation reflects each one. This also catches leftover debugging code and unfixed TODO items.
  • Higher-level comments are easier to maintain: they don’t reflect details, so only changes in overall behavior affect them.
  • Minimal-change mindset: the tactical habit of the smallest edit, often justified by fear of breaking unfamiliar code.
  • Narrowest scope rule: place each implementation comment just above the first line of the code it describes.
  • Abstraction-by-distance rule: “the farther a comment is from the code it describes, the more abstract it should be.”
  • Self-evident stale references: if a pointer comment’s target moves, readers notice it is missing. Stale duplicated text gives no such warning.
  • Think of every modification as a chance to improve the design “at least a little bit.”
  • Put documentation where developers are most likely to see it. The commit log is rarely that place.
  • “It’s important that readers can easily find all the documentation needed to understand your code, but that doesn’t mean you have to write all of that documentation.”
  • The smallest possible change: tactical programming applied to maintenance.
  • Interface comments in a separate header file: far from the code, so they go stale.
  • One big comment at the top describing every phase in detail.
  • Details only in the commit message.
  • Explaining the callee before each call.
  • Copying protocol or manual documentation into code.
// We proceed in three phases:
// Phase 1: Find feasible candidates
// Phase 2: Assign each candidate a score
// Phase 3: Choose the best, and remove it
  • What it demonstrates: an overview of the strategy at the top of a method, with the details for each phase placed just above that phase’s code.
// Implements the Foo command; see the user manual for details.
  • What it demonstrates: referencing external documentation instead of duplicating it.

A bug fix in unfamiliar code, done strategically (an illustration that applies the chapter’s steps).

  1. You need to fix a bug, and the quick fix is to add one more special-case check in the caller.
  2. Ask whether the current design is still the best one given what the bug revealed. Suppose the bug shows that one piece of knowledge is split between two classes. The strategic fix moves that knowledge into one class.
  3. If the full refactoring would take three months, look for a version that is almost as clean and takes a couple of days, or do the quick fix now and get time scheduled for the cleanup.
  4. Record why the change was needed in a comment next to the code, not only in the commit message. If there is a bug report, refer to it rather than repeating it.
  5. If the tricky behavior affects several places, document it once (for example next to the variable’s declaration) and add “See the comment in xyz” pointers elsewhere.
  6. Before committing, scan the diff to make sure every change is reflected in the comments, and that no debugging code or TODO is left behind.
  1. Aim to leave the system as if the change had been designed in from the start.
  2. Improve something in the design with every modification.
  3. When constraints force a shortcut, find the cleanest option that fits and schedule the rest.
  4. Keep comments next to the code they describe, at the narrowest scope.
  5. Put lasting explanations in the code, not in commit messages.
  6. Document each decision once and point to it; check the diff before every commit.
  • Ch 1: incremental development means continuous redesign.
  • Ch 3: strategic programming and the investment mindset.
  • Ch 12: the excuse that comments go out of date.
  • Ch 13: designNotes, cross-module comments, and bug-tracker references.
  • Ch 19: agile development and the risk of tactical increments.