Chapter 01 · Week 1

Reading Code Like Scripture

What does it mean to read carefully?

Chapter 1 — Reading Code Like Scripture

“The best programmers I know spend most of their time reading, not writing.” — Kent Beck

“Open my eyes, that I may behold wondrous things out of your law.” — Psalm 119:18


Why This Matters

In Coding 1, you learned to write code. You sat down in front of a blank file and made something appear. The pleasure of that — the empty buffer becoming a working program — was the central pleasure of the course.

Coding 2 inverts the work.

A working programmer at year five spends more hours reading code than writing it. A working senior engineer at year ten spends more hours reading than the junior spends writing. The proportion only gets worse — or better, depending on how you look at it — as you become the person other people’s code lands on for review, integration, debugging, and rescue.

Now add AI to the picture. In Phase 2 of this book, you will be asking a machine to write large blocks of code for you. The machine is fast. The machine is fluent. The machine is also frequently subtly wrong. The only thing standing between you and shipping a fluent, plausible, subtly-wrong program is your ability to read what came back and recognize what it actually does — not what it looks like it does.

That is the skill of this chapter. We are training your reading muscle before we train your prompting muscle, because the prompting muscle is worthless without it.

The Christian student already knows part of the move. The church has, for two thousand years, taken a body of text seriously enough to read it slowly, in community, with cross-references, in the original language when possible, with the assumption that the careful reading reveals more than the casual one. That discipline — applied to code — is what this chapter is asking from you.

You are not going to write a single line of new logic in this chapter. You are going to read.


1.1 — What “Reading Code” Actually Means

When a beginner says “I read the code,” they usually mean “my eyes passed over it.” When a senior says “I read the code,” they mean something specific. Roughly, in order:

  1. What does this program do? One sentence. Not “it takes inputs and produces outputs” — that’s every program. The actual job.
  2. What is the shape of the program? Which classes exist, what does each one own, who calls whom.
  3. Where does data come from and where does it go? Inputs, transformations, outputs, side effects.
  4. What are the assumptions? What does this code believe about its inputs that nobody wrote down?
  5. What would break it? Edge cases the author handled, and the ones they didn’t.
  6. What questions would a reviewer have? Not nitpicks. Real architectural questions.

If you finish reading and you cannot answer those six questions, you have not read the code. You have looked at the code.

Coach’s Note — A read that produces no questions is not a careful read — it’s a flattering one. Real reading produces a list of things you don’t yet understand. The list shrinks as you re-read. The fact that the list exists is the proof you were paying attention.


1.2 — A Worked Example

Here is a 40-line Java program. Read it once at normal speed. Don’t try to understand every line. Just get the shape.

import java.util.ArrayList;
import java.util.Scanner;

public class AttendanceLog {
    private ArrayList<String> names;
    private int capacity;

    public AttendanceLog(int capacity) {
        this.capacity = capacity;
        this.names = new ArrayList<>();
    }

    public boolean checkIn(String name) {
        if (names.size() >= capacity) {
            return false;
        }
        names.add(name);
        return true;
    }

    public int count() {
        return names.size();
    }

    public boolean isPresent(String name) {
        for (String n : names) {
            if (n == name) return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        AttendanceLog log = new AttendanceLog(3);
        for (int i = 0; i < 4; i++) {
            System.out.print("Name: ");
            log.checkIn(in.nextLine());
        }
        System.out.println("Count: " + log.count());
        System.out.println("Maya present? " + log.isPresent("Maya"));
        in.close();
    }
}

Now apply the six questions from §1.1.

What does this program do? It reads names from the user and tracks them in a fixed-capacity attendance log, then reports the count and whether one specific name was checked in.

What is the shape? One class, AttendanceLog, with a list of names, a capacity, and four methods (checkIn, count, isPresent, and main).

Where does data come from and where does it go? Names come from System.in via Scanner. They are stored in an ArrayList<String>. The program writes summary lines to System.out.

What are the assumptions? That the user types one name per line. That capacity is a positive number. That checkIn returning false on a full log is acceptable silent behavior. That names are case-sensitive.

What would break it? A capacity of zero or negative. An empty input line. A check-in attempt past capacity (returns false but main doesn’t react). And — the deep bug — isPresent uses == to compare strings instead of .equals(). Remember Chapter 13 of Coding 1 — Chapter 7 if you took the accelerated edition: == on Strings compares reference identity, not content. isPresent("Maya") will almost always return false even when “Maya” was checked in, because the literal "Maya" in main is a different String object than the one Scanner produced.

What questions would a reviewer have?

  • Why does main loop four times against a capacity of three? Was that intentional (testing the over-capacity path) or a bug?
  • Should checkIn reject duplicate names? Right now it silently allows them.
  • Should count() be getCount() for naming consistency with getters?

That is a careful read.

Coach’s Note — Notice how much of the work was Coding 1 vocabulary in your back pocket. == vs .equals(). Capacity-versus-actual checks. ArrayList iteration. The reading muscle is your prior knowledge actively scanning for trouble. Coding 1 is the gym. This is the game.


1.3 — Java Naming Conventions (And What They Tell You)

When you read unfamiliar Java, the names alone tell you most of the story — if the author followed conventions. Java’s are stricter and more universally observed than C++‘s.

KindConventionExample
ClassPascalCaseAttendanceLog, String, Scanner
MethodcamelCase verb-phrasecheckIn, getBalance, isPresent
Field / localcamelCase nounnames, capacity, totalScore
ConstantUPPER_SNAKE_CASEMAX_CAPACITY, DEFAULT_TIMEOUT
Packagelowercase.dot.separatedjava.util.concurrent
Boolean accessorstarts with is, has, canisPresent, hasNext, canRead
Getterstarts with getgetBalance, getOwner
Setterstarts with setsetBalance, setOwner

What a reader does with this:

  • A name in PascalCase? It’s a type. Find its definition.
  • A method starting with is/has/can? It returns a boolean. Use it in an if.
  • A name in UPPER_SNAKE_CASE? It’s a static final constant. Trace where it was set.
  • A package name? It’s a directory path in disguise.

When the author breaks the convention, that itself is information. A method named Process() (capital P) is probably either a bug, a port from another language, or a deliberate signal of something unusual. Be suspicious.

Coach’s Note — Bad names are not “style.” Bad names are bugs in your future self’s reading speed. The Java standard library is the gold standard here — read String, ArrayList, and Scanner and you will absorb the conventions whether you mean to or not.


1.4 — Reading Method Signatures First

When you open an unfamiliar class, read the signatures before you read the bodies. The signatures tell you the shape of the class. The bodies are the implementation details that fill the shape in.

For AttendanceLog from §1.2, the signatures alone tell you almost everything:

public AttendanceLog(int capacity)
public boolean checkIn(String name)
public int count()
public boolean isPresent(String name)

From that, with no bodies at all, you can already tell:

  • The constructor takes a capacity. So the log has a fixed size determined at creation.
  • checkIn returns boolean. So it can succeed or fail — and the caller is expected to check.
  • count() returns int. Current size, presumably.
  • isPresent returns boolean for a given name. Lookup.

The bodies confirm or contradict your guess. If your guess from the signature is right, the body should be boring. If the body surprises you, that’s where the bugs hide.

This is the move: read the headline; check the article confirms it. If the article disagrees with the headline, the headline is lying.


1.5 — Reading the Standard Library: Javadocs

Java has, hands down, one of the best documented standard libraries of any language. Every class, every method, every parameter, every exception is described in a structured format called Javadoc. You will look at Javadocs for the rest of your career.

The canonical place to read them is the official API documentation. For Java 17 LTS (the version this course uses), it lives at:

https://docs.oracle.com/en/java/javase/17/docs/api/

Bookmark it.

Here is a fragment of the Javadoc for String.substring:

public String substring(int beginIndex, int endIndex)

Returns a string that is a substring of this string. The substring
begins at the specified beginIndex and extends to the character at
index endIndex - 1. Thus the length of the substring is
endIndex - beginIndex.

Parameters:
    beginIndex - the beginning index, inclusive.
    endIndex   - the ending index, exclusive.

Returns:
    the specified substring.

Throws:
    IndexOutOfBoundsException - if the beginIndex is negative,
        or endIndex is larger than the length of this String
        object, or beginIndex is larger than endIndex.

Notice what’s there. A one-sentence purpose. Each parameter explained. The return value. Every exception that can be thrown. This last part is gold — when you’re reading a method that calls substring, you now know it can blow up with IndexOutOfBoundsException if your indices are off. That’s a contract the standard library makes with you (Chapter 2’s whole topic).

When you encounter an unfamiliar standard-library method in code you’re reading, stop and look it up. Don’t guess. Don’t pattern-match. The doc is two clicks away and it will tell you the truth.

Coach’s Note — Get used to reading Javadocs in their natural habitat — the IDE tooltip, the official docs site, or the source jar. The pattern “I know what this probably does” is exactly the pattern that gets junior engineers in trouble. The senior move is to look it up the first six times you see it, until you no longer have to.


1.6 — Reading Code With a Pen in Your Hand

You cannot read a 500-line program by staring at it. You need to take notes.

Here is the method that works — call it the comprehension brief. It’s the same artifact Project 1 will ask you to produce.

For a moderate-sized Java program, in this order:

  1. Open the file. Read no more than the imports and class signatures. Write down: what classes exist? What do their names suggest?
  2. For each class, read the field declarations. Write down: what state does this class own?
  3. For each class, read the method signatures. Write down: what can this class be asked to do?
  4. Read main (or the entry point). Write down: what’s the actual sequence of operations?
  5. Trace one realistic input through the program from start to finish. Where does it go? What does it touch? What’s the final output?
  6. List three concrete questions a reviewer would have. Not “this could be better” — actual specific questions like “what happens if the input file is empty?” or “is this method called from anywhere else?”
  7. List two bugs or fragilities you spotted along the way.

If you do those seven steps with any program under 500 lines, you have read it. That is the deliverable for Project 1.

Coach’s Note — Steps 1 through 3 might feel like procrastination — “I haven’t started reading the code yet, I’m just looking at headers!” Resist that feeling. The skeleton is the most important part of the read. A program whose skeleton you understand is a program whose body you can fill in. A program whose body you’ve memorized but whose skeleton you can’t draw is a program you have not understood.


1.7 — Bible Reading, Code Reading

The apologetic frame for this chapter is what does it mean to read carefully?

The church has answered this question for two thousand years, and it has a vocabulary worth borrowing. Medieval monks talked about lectio divina — divine reading — in four steps: lectio (read), meditatio (meditate), oratio (pray), contemplatio (contemplate). The four steps map onto careful reading of any serious text, sacred or not. Strip out the explicitly prayerful steps for code, and you still have lectio (read it through), meditatio (turn the parts over, ask what they mean, cross-reference), and a final step we might call interrogatio — pose the hard questions.

The Lutheran confessional tradition adds another move: Scripture interprets Scripture. A hard passage in one place is illuminated by a clearer passage in another. The same is true in a codebase. A confusing method becomes clearer when you find the three other places it’s called from. A puzzling field becomes obvious once you see where it’s written. The careful reader of code is constantly cross-referencing — Scriptura sui ipsius interpres — code is its own interpreter.

This is not a metaphor reaching for cleverness. It is a real claim: the discipline of reading the Bible slowly is the same kind of discipline as the discipline of reading code slowly. Both reward patience over speed. Both punish the assumption that the surface meaning is the only meaning. Both expect the reader to come back to the text again next week and find more than they found this week.

If your tradition trained you to read Scripture this way, you have a head start. If it didn’t, you can still train the muscle. It is the same muscle either way.

Coach’s Note — I am not asking you to treat code as Scripture. Code is human-made and falls short of Scripture’s authority — sola scriptura, after all. But the attention a Christian student brings to Scripture is exactly the attention the working engineer’s career will demand. You already have the move. Apply it.


1.8 — Reading AI-Generated Code Differently

A preview of Phase 2, since the rest of the course assumes it.

Code an AI writes for you looks fluent. The variable names are sensible. The structure follows convention. The Javadoc, if you asked for it, will be elegant. None of that means the code is correct.

The specific failure modes you will be hunting in Phase 2:

  • Hallucinated APIs. The code calls String.reverse() or ArrayList.sortBy(...) — methods that don’t exist in Java. The shape is plausible. The method is fictional.
  • Off-by-one errors. A loop that runs from 0 to n instead of 0 to n-1, or vice versa. Pythonic indexing in Java code, or vice versa.
  • Wrong null handling. A method that returns null when it should throw, or throws when it should return empty.
  • Subtle precedence errors. Operator precedence the author didn’t think about. Implicit numeric promotion that loses precision.
  • Plausible-sounding wrong defaults. A constant set to 1000 because “that’s usually fine” when the actual answer should be configurable.

You will not catch any of those by glancing. You will catch them by reading the code the way this chapter is teaching you to. The same skill that lets you read a human’s code lets you audit a machine’s code. That is why we are training it before we touch AI.


1.9 — Common Bugs (Reading Edition)

These are the bugs your reading introduces — places where you think you understood the code but didn’t.

Bug: You read a method and thought it modified the object. It actually returns a new object and discards the old one. Example: name.toLowerCase(); — does nothing if you don’t assign the result. String is immutable. Fix: Always check return types. If the method returns the same type as the receiver, suspect it returns a new instance rather than mutating.


Bug: You assumed two if branches were mutually exclusive, but a missing else lets both run.

if (x > 0) doA();
if (x > 0) doB();   // both run if x > 0

Fix: Read every if and ask: is the next if an else if in disguise? If yes, the author made an error you should flag.


Bug: You read a class and assumed the field was private. It was package-private (no modifier). Example: int balance; — no private, no public. Anything in the same package can read and write it. Fix: Note the access modifier on every field. Missing modifier means package-private, not private.


Bug: You confused == and .equals() while reading and assumed the author had it right. Fix: Every == between two object-typed variables is a red flag. Verify the author meant reference equality. Almost always, they didn’t.


Bug: You read the loop bound as < n when it was <= n (or vice versa) and assumed there was no off-by-one. Fix: Read the loop bound twice. Out loud if necessary. Off-by-one errors hide in the place you expect them least.


Bug: You skimmed the imports and missed that the class uses java.sql.Date instead of java.util.Date. They behave differently. Fix: Read the imports. Two classes with the same simple name from different packages is a classic source of confusion.


1.10 — Reps

Open the exercises for the full set. Every rep this week is a reading rep — you do not write logic this week. You read, you annotate, you predict, and you confirm.

A preview:

  • Rep 1 — Predict the output of a 20-line program before running it.
  • Rep 2 — Read the same program. List its three assumptions and two fragilities.
  • Rep 3 — Look up a Java standard-library method you’ve never used. Summarize its Javadoc in two sentences.
  • Rep 10 — Read a 100-line program cold and produce a comprehension brief.

Do every one. Phase 1 has no AI to lean on. The reading muscle is the foundation of everything Phase 2 will ask of you.


1.11 — This Week’s Project

You’re ready for Project 1 — The Code Comprehension Brief, in Project 1.

You will receive a 200-line unfamiliar Java program. You will write a one-page brief that explains what it does, what each class is for, how data flows through main, three questions a reviewer would have, and two bugs or fragilities. The Medium tier asks you to propose a refactor. The Hard tier asks you to actually do the refactor — without changing behavior — and confirm with the provided test suite.

This is the first project in the book. There is no code to write at the Normal tier. Don’t underestimate it. A good brief is harder than it looks. A bad brief is worth nothing.


1.12 — Coach’s Final Word for Week 1

Coding 1 made you a writer. Coding 2 is going to make you a reader, then a director, then a writer again with both other skills compounding on top.

This week, you read. You do not write logic. You do not optimize anything. You do not refactor anything (unless you push to the Hard tier of P1, and even there you preserve behavior). You produce a one-page document that proves you understood a program someone else wrote.

If you find this boring: that’s the gap. Close it.

If you find this hard: that’s the gap. Close it.

If you find this easy: you have a head start, and the rest of Phase 1 will pull you forward anyway. Don’t coast.

The work of reading is the work of every senior engineer you will ever respect. Welcome to Coding 2.

See you on Monday.


Up next: Read the exercises and complete every rep. Then open Project 1 and write your first brief. After that, Chapter 2 — contracts and specifications.