Skip to main content
Engineering craft beyond tooling — design patterns, refactoring, code review, advanced testing strategy, and reading code you did not write.

Software Engineering Craft

Engineering craft beyond tooling — design patterns, refactoring, code review, advanced testing strategy, and reading code you did not write.

Reading Legacy Code & Code Review Craft

Engineers spend more time reading code than writing it. Most curricula invert that ratio — output-side skills dominate, input-side skills are presumed. The result: engineers who can write clean greenfield code but stall the moment they meet a 50,000-line repo they did not author. This topic is the input side.

Why Reading Is Harder

Writing code you have already thought through is mechanical. Reading code forces you to reconstruct someone else’s intent under three layers of noise: outdated comments, defensive layers for cases that turned out impossible, and shapes chosen for reasons long obsolete. The skill is triage under uncertainty — decide quickly what is load-bearing and what is debris.

The Three-Pass Method

Reading a single entry point top-to-bottom is the slow way in. Instead:

  1. Map pass — list every public entry point (HTTP handlers, exported functions, CLI commands). One line each. Now you know the surface.
  2. Trace pass — pick one entry point important to your task. Walk its call tree down two levels, ignoring everything else. Write a one-paragraph story of what it does.
  3. Detail pass — only resolve the question you actually came with. Skip the rest; you can return when needed.

The method scales from a 100-file library to a million-line monorepo because each pass is bounded by the next question, not by the size of the codebase.

Git as a Comprehension Tool

git blame tells you the last line touched. git log -L 'function foo:' shows every commit that touched a function. Used together they answer the question code can’t answer itself: why does this line look this way?

  • git log -S "string" — find the commit that introduced or removed a string. Gold for “when did this error message appear?”
  • git log --follow path — history through renames. Useful before a function moved.
  • git bisect — binary-search for the regression. Even a historian’s tool turns into a forensics tool the moment a bug appears.

The commit message is often the only surviving design note. Read histories liberally; the spec is the codebase, but the design rationale is the log.

Characterization Tests as Reading Aids

A characterization test pins current behaviour — it asserts not “this is correct” but “this is what it does today”. Writing one forces you to observe instead of theorise: the test fails the moment your mental model disagrees with the machine. Three or four characterization tests around a hairy function teach you more about it in an hour than re-reading for a day.

Pin → refactor → verify the pins still pass: that is the safe path through any function you do not yet trust.

Code Review as Craft

A review serves two purposes — catch defects and teach — and a good review balances both. The mechanics:

  • Read the diff twice, then the surrounding code, then ask one question. Premature critique flags symptoms; considered critique flags causes.
  • Group comments by severity: blocking (P0), should-fix (P1), suggestion (P2), nit (nit). The author reads severity before code; unlabelled comments waste their time.
  • Pair praise with critique: a review that’s only negative burns out the author. Saying “this is a clean abstraction” is signal, not flattery — it tells the author what to do more of.

The Reviewer’s Checklist

A short, repeatable checklist beats an ad-hoc one:

LayerQuestion
CorrectnessWill this produce the right answer for the documented inputs? What about the undocumented ones?
ClarityWill a reader in six months understand this without asking the author?
RiskWhat breaks if this is wrong? How loud is the failure?
TestsDo the tests cover the new behaviour? Do they fail if the code is reverted?
StyleDoes this match the file’s conventions? (Inconsistency is louder than any style choice.)

Common Review Failure Modes

  • The “LGTM” review: a quick approval without reading. Catches nothing, signals nothing. Worse than no review — it pretends to be a guardrail.
  • The bikeshed review: 200 words on naming, zero on the new I/O call that blocks the event loop. Loud on trivia, silent on risk.
  • The architecture-from-the-trenches review: large.btnAdd(“while you’re here, let’s also redesign this module”). Scope creep under review cover blocks the author more than it helps.
  • The comment drive-by: a terse “this is bad” with no suggested action. The author can neither fix nor rebut; only guess.

Each failure mode is correctable by one habit: propose, don’t just describe. “This loop could exit early at line 23 if x is null” beats “this loop is inefficient”.

Practice Trajectory

  1. Pick a function in a real repo, write a characterization test for it, then commit it. Re-read the function after writing the test; notice what changed in your model.
  2. Run git blame on a confusing line. Find the commit. Read the message. Re-evaluate whether the line still makes sense today.
  3. Audit your last ten code reviews: how many comments were P0-P1 vs P2 or nit? If the ratio is nit-heavy, you are burning author attention.
  4. Review a pull request with the only goal of finding one defect and one piece of genuine praise. Both must be specific to count.
  5. For a function in your own code, write the one-paragraph story the way you would for someone else’s. If you cannot, your function has no single responsibility.

When It’s the Right Tool

SituationTakeaway
Entering an unfamiliar codebaseThree-pass reading beats a week of trial-and-error
Debugging a regressiongit bisect plus a characterization test is the surgical fix
Reviewing juniorsBe the reviewer you wish you’d had — propose, don’t just describe
Reviewing seniorsTrust their judgement on architecture; focus your critique on risk and clarity