<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>README — code-review</title>
<link>https://readme.news/tags/code-review/</link>
<atom:link href="https://readme.news/tags/code-review/feed.xml" rel="self" type="application/rss+xml"/>
<description>README pieces tagged code-review.</description>
<language>en-us</language>
<lastBuildDate>Thu, 01 Oct 2026 13:20:31 +0000</lastBuildDate>
<item><title>The load-bearing comment</title><link>https://readme.news/the-load-bearing-comment/</link><guid isPermaLink="true">https://readme.news/the-load-bearing-comment/</guid><pubDate>Tue, 04 Aug 2026 09:00:00 +0000</pubDate><description>Most comments restate the code and rot. A few carry information the code cannot express, and those are worth defending.</description><content:encoded><![CDATA[<p>The advice that comments should explain <em>why</em> rather than <em>what</em> is correct and too abstract to act on. Here is the concrete version: a comment earns its place when it records information that could not have been derived from reading the code, and that someone would otherwise have to rediscover.</p>
<p>Everything else is decoration that will drift out of sync.</p>
<h2 id="the-four-that-earn-their-place">the four that earn their place<a class="anchor" href="#the-four-that-earn-their-place" aria-label="link to this section">#</a></h2>
<p><strong>The constraint that came from outside.</strong></p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python"># The vendor's API rejects batches over 500 even though their docs say 1000.
# Confirmed with their support, ticket #48812, March 2026.
BATCH_SIZE = 500</code></pre></div>
<p>Nothing in the code can tell you this. Without it, someone raises the number back to 1000 in eighteen months, ships it, and spends a day debugging.</p>
<p><strong>The alternative that was tried and failed.</strong></p>
<div class="code"><span class="code-lang">go</span><pre><code class="lang-go">// Tried sync.Map here first. It was slower for this access pattern — writes
// dominate and the keys are hot, which is the case sync.Map is worst at.
// Benchmark: bench/cache_test.go, BenchmarkHotKeys.</code></pre></div>
<p>This is the highest-value comment there is, because it stops the next person repeating your experiment. It converts a day of their work into a paragraph.</p>
<p><strong>The non-obvious ordering.</strong></p>
<div class="code"><span class="code-lang">javascript</span><pre><code class="lang-javascript">// Must run before initAuth(): the session store reads the feature flag cache,
// which is populated here. Reversing these fails silently — the user gets the
// default flag set rather than an error.</code></pre></div>
<p>"Fails silently" is the part that matters. A comment marking a trap is worth more than one marking a happy path.</p>
<p><strong>The deliberate weirdness.</strong></p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">// Deliberately not using the iterator: this is on the hot path and the
// bounds checks cost ~8% on the profile. See PERF-221.</code></pre></div>
<p>Without this, a well-meaning reviewer "cleans it up" and quietly regresses performance. Deliberate ugliness that isn't labelled reads as accidental ugliness, and accidental ugliness gets fixed.</p>
<h2 id="the-ones-that-do-not">the ones that do not<a class="anchor" href="#the-ones-that-do-not" aria-label="link to this section">#</a></h2>
<p><strong>Restating the line.</strong> <code>// increment counter</code> above <code>counter += 1</code>. This is the canonical example and it is still everywhere.</p>
<p><strong>Section headers in a long function.</strong> <code>// ---- validation ----</code> is a request for an <code>extract function</code> refactor wearing a disguise.</p>
<p><strong>Commented-out code.</strong> Delete it. Git has it. A block of dead code with no note is a question nobody can answer: is this a work in progress, a rollback, or something someone forgot?</p>
<p><strong>Changelogs in the file header.</strong> <code>// 2019-03-04 JS: added retry logic</code>. That is what the commit log is for, and it is accurate there.</p>
<p><strong>Comments that duplicate the type.</strong> <code>// returns a list of user IDs</code> above <code>fn user_ids() -&gt; Vec&lt;UserId&gt;</code>. The signature already said it, and the signature cannot go stale.</p>
<h2 id="the-decay-problem">the decay problem<a class="anchor" href="#the-decay-problem" aria-label="link to this section">#</a></h2>
<p>Every comment is a claim that is not checked by anything. The code changes; the comment does not; now it is actively misleading, and a misleading comment is worse than no comment because it is trusted.</p>
<p>Three things reduce the decay:</p>
<p><strong>Put it as close to the fact as possible.</strong> A comment above the line it describes survives longer than one at the top of the file describing behaviour forty lines down.</p>
<p><strong>Prefer things that are checked.</strong> A well-named function, a type, a test with a descriptive name, an assertion — all of these carry the same information and break when they become false. Reach for those first; comment only what none of them can express.</p>
<p><strong>Reference something durable.</strong> A ticket, a benchmark file, a commit hash, a support ticket number, a link to the vendor's documentation. Then a reader who doubts the comment can check it rather than guess.</p>
<h2 id="the-test">the test<a class="anchor" href="#the-test" aria-label="link to this section">#</a></h2>
<p>Before writing a comment, ask: <strong>could I encode this in a name, a type, or a test instead?</strong></p>
<p>If yes, do that — it will be checked, and the comment will not.</p>
<p>If no — if the information genuinely lives outside the code, in a vendor's behaviour or a decision someone made or an experiment that failed — write it down. That is exactly the case comments exist for, and those comments will still be earning their keep in a decade.</p>]]></content:encoded></item><item><title>What I look for in a codebase in the first hour</title><link>https://readme.news/what-i-look-for-in-a-codebase-in-the-first-hour/</link><guid isPermaLink="true">https://readme.news/what-i-look-for-in-a-codebase-in-the-first-hour/</guid><pubDate>Fri, 24 Jul 2026 09:00:00 +0000</pubDate><description>A checklist for assessing an unfamiliar codebase quickly — for a job, a due diligence, or a project you inherited.</description><content:encoded><![CDATA[<p>You have an hour with an unfamiliar codebase and you need to form a judgment: is this healthy, what will it cost to work in, what is the risk.</p>
<p>Here is the order I go in, and what each thing tells you.</p>
<h2 id="1-can-i-run-it-15-minutes">1. can I run it? (15 minutes)<a class="anchor" href="#1-can-i-run-it-15-minutes" aria-label="link to this section">#</a></h2>
<p>Clone it. Follow the README. Start a timer.</p>
<p>This is the single most informative test and most people skip it in favor of reading code.</p>
<ul><li><strong>Under 10 minutes to a running application:</strong> the team cares about developer experience, and probably about a lot of other things.</li><li><strong>An hour, with several undocumented steps:</strong> onboarding costs a week and every new hire pays it.</li><li><strong>You cannot get it running:</strong> the only people who can work on this are the ones who already have it working. This is a serious risk and it is invisible from the outside.</li></ul>
<p>Note every step that failed. That list <em>is</em> the health assessment.</p>
<h2 id="2-the-test-suite-10-minutes">2. the test suite (10 minutes)<a class="anchor" href="#2-the-test-suite-10-minutes" aria-label="link to this section">#</a></h2>
<p>Run it. Then look at it.</p>
<p><strong>Does it pass?</strong> On a clean checkout, first try. If not, that tells you the team has normalized a red build.</p>
<p><strong>How long?</strong> Under two minutes is excellent. Over ten and people have stopped running it locally.</p>
<p><strong>What is the ratio of assertions to setup?</strong> Read three test files. If setup dominates, the code is heavily coupled.</p>
<p><strong>Are there tests for the error paths?</strong> Almost nobody writes these and their presence is a strong positive signal.</p>
<p><strong>Is there a <code>skip</code> or <code>xfail</code> graveyard?</strong> Count them. A pile of disabled tests means the suite has been losing a slow argument with reality.</p>
<h2 id="3-the-shape-of-the-repository-5-minutes">3. the shape of the repository (5 minutes)<a class="anchor" href="#3-the-shape-of-the-repository-5-minutes" aria-label="link to this section">#</a></h2>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">tokei .            # lines by language
git log --oneline | wc -l
git log --format='%an' | sort | uniq -c | sort -rn | head</code></pre></div>
<p><strong>Contributor concentration.</strong> If one person wrote 80% of it and they left, that is the largest risk in the codebase, larger than anything technical.</p>
<p><strong>Language sprawl.</strong> Four languages in a small project usually means four sets of tooling and nobody who understands all of it.</p>
<p><strong>Directory structure.</strong> Does it reflect the domain or the framework? <code>models/</code>, <code>views/</code>, <code>controllers/</code> tells you nothing about what the software does. <code>billing/</code>, <code>inventory/</code>, <code>shipping/</code> tells you everything.</p>
<h2 id="4-the-largest-files-5-minutes">4. the largest files (5 minutes)<a class="anchor" href="#4-the-largest-files-5-minutes" aria-label="link to this section">#</a></h2>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">find . -name '*.py' -not -path '*/.venv/*' | xargs wc -l | sort -rn | head -20</code></pre></div>
<p>Every codebase has a few enormous files. Open the biggest one.</p>
<ul><li><strong>Is it generated?</strong> Fine, ignore it.</li><li><strong>Is it a god object?</strong> A 4,000-line service class is where all the complexity accumulated and where all the bugs live.</li><li><strong>When was it last changed?</strong> <code>git log -1</code> on it. If it is huge and changes weekly, that is the hot spot, and any work you do will touch it.</li></ul>
<h2 id="5-dependencies-5-minutes">5. dependencies (5 minutes)<a class="anchor" href="#5-dependencies-5-minutes" aria-label="link to this section">#</a></h2>
<p><strong>How many?</strong> Compare against similar projects. An unusual number in either direction is worth understanding.</p>
<p><strong>How old?</strong> Anything more than two major versions behind is an upgrade project waiting for you.</p>
<p><strong>Anything abandoned?</strong> Check the largest ones for last release date. A critical dependency with no release in three years is a fork you have not made yet.</p>
<p><strong>Anything surprising?</strong> A cryptography library nobody has heard of. A vendored copy of something. A dependency on a specific fork.</p>
<h2 id="6-the-commit-history-10-minutes">6. the commit history (10 minutes)<a class="anchor" href="#6-the-commit-history-10-minutes" aria-label="link to this section">#</a></h2>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">git log --oneline -50</code></pre></div>
<p><strong>Message quality.</strong> "fix", "wip", "asdf" versus real descriptions. This is a direct readout of the engineering culture and it is remarkably predictive.</p>
<p><strong>Commit size.</strong> Are they atomic, or is every commit a 3,000-line dump?</p>
<p><strong>Is there review?</strong> Merge commits from pull requests, or direct pushes to the main branch?</p>
<p><strong>Cadence.</strong> Steady, or bursts separated by silence?</p>
<h2 id="7-the-things-that-are-missing-5-minutes">7. the things that are missing (5 minutes)<a class="anchor" href="#7-the-things-that-are-missing-5-minutes" aria-label="link to this section">#</a></h2>
<p>Frequently the most informative part.</p>
<ul><li><strong>No CI configuration.</strong> Nothing is checked automatically.</li><li><strong>No linter or formatter config.</strong> Every file is a different style and every review argues about it.</li><li><strong>No <code>CONTRIBUTING.md</code> or equivalent</strong> on a project with multiple contributors.</li><li><strong>No <a class="xref" href="/the-unreasonable-effectiveness-of-a-changelog/" title="The unreasonable effectiveness of a changelog">changelog</a>.</strong> Nobody knows what changed between versions.</li><li><strong>No architecture documentation of any kind.</strong> The system exists only in people's heads.</li><li><strong>No <code>.env.example</code>.</strong> You cannot configure it without asking someone.</li></ul>
<h2 id="8-one-real-feature-end-to-end-10-minutes">8. one real feature, end to end (10 minutes)<a class="anchor" href="#8-one-real-feature-end-to-end-10-minutes" aria-label="link to this section">#</a></h2>
<p>Pick a user-visible feature and trace it from the entry point to the database.</p>
<p>This is the highest-value ten minutes. You learn: how many layers, how much indirection, where the business logic lives, whether the abstractions are consistent, and whether you could add a similar feature without asking anyone.</p>
<p>If you cannot follow it in ten minutes, neither can anyone else, and every change will be expensive.</p>
<h2 id="the-summary-judgment">the summary judgment<a class="anchor" href="#the-summary-judgment" aria-label="link to this section">#</a></h2>
<p>After an hour I can usually say:</p>
<ul><li><strong>How long until a new engineer is productive.</strong> (From step 1 and 8.)</li><li><strong>Whether changes are safe.</strong> (From step 2.)</li><li><strong>Where the risk is concentrated.</strong> (From steps 3 and 4.)</li><li><strong>What the team values.</strong> (From steps 6 and 7.)</li></ul>
<p>None of that requires understanding what the software does. It is all structural, and structure is what determines the cost of working in something.</p>]]></content:encoded></item><item><title>Code review comments that change things</title><link>https://readme.news/code-review-comments-that-change-things/</link><guid isPermaLink="true">https://readme.news/code-review-comments-that-change-things/</guid><pubDate>Mon, 06 Jul 2026 09:00:00 +0000</pubDate><description>Most review comments are noise or nitpicks. A small taxonomy of the ones that are worth writing.</description><content:encoded><![CDATA[<p>Most code review comments do not change the code, and of the ones that do, most change something that did not matter.</p>
<p>Here is a taxonomy of comments worth writing, roughly in descending order of value.</p>
<h2 id="the-ones-worth-writing">the ones worth writing<a class="anchor" href="#the-ones-worth-writing" aria-label="link to this section">#</a></h2>
<p><strong>"This will break when X."</strong> The highest-value comment there is. A specific failure scenario the author did not consider.</p>
<blockquote><p>"If two requests hit this concurrently, both will pass the existence check and both will insert. We saw this exact bug in the invoicing path last year."</p></blockquote>
<p>Concrete, falsifiable, and it comes with evidence.</p>
<p><strong>"This contradicts how we do it elsewhere."</strong> Consistency has real value and the author frequently does not know the precedent exists.</p>
<blockquote><p>"<code>orders/</code> uses the repository pattern for this. Worth matching, or is there a reason to differ here?"</p></blockquote>
<p>Note the question at the end. Sometimes there is a reason and you have just learned something.</p>
<p><strong>"I don't understand this."</strong> Underrated. If a reviewer with context cannot follow it, a stranger in two years will not either.</p>
<p>This is not an admission of inadequacy. It is a measurement of the code's clarity, and it is a measurement only a reader can take.</p>
<p><strong>"What happens if this fails?"</strong> The most consistently productive question in code review. Error paths are the least-considered part of most changes and the most-exercised part in production.</p>
<p><strong>"Is this the right layer?"</strong> Business logic in a controller, presentation logic in a model, a database call in a template. Structural, cheap to fix now, expensive later.</p>
<p><strong>"This is good and here is why."</strong> Genuinely valuable and almost never written. Naming what worked teaches the pattern and it makes the critical comments land better, because they arrive from someone who is paying attention rather than someone who is looking for problems.</p>
<h2 id="the-ones-not-worth-writing">the ones not worth writing<a class="anchor" href="#the-ones-not-worth-writing" aria-label="link to this section">#</a></h2>
<p><strong>Anything a formatter or linter handles.</strong> If you are commenting on spacing, quotes, or import order, fix your tooling instead. A human enforcing mechanical rules is a broken process.</p>
<p><strong>Style preferences without a reason.</strong> "I would have used a map here" is not a review comment, it is a preference. If there is a reason — clarity, performance, consistency with a convention — say the reason. If there is not, do not send it.</p>
<p><strong>Speculative generality.</strong> "What if we later need to support multiple currencies?" Usually they will not, and building for it costs now. If you genuinely believe it, say what makes you believe it.</p>
<p><strong>A redesign in a review comment.</strong> If the approach is fundamentally wrong, that is a conversation, not a comment thread. Comments are for improving an approach; a different approach needs a discussion, and doing it in review comments after the work is done is the most expensive possible time.</p>
<p>That failure is on the process, not the reviewer: the design should have been discussed before implementation.</p>
<h2 id="how-to-phrase-things">how to phrase things<a class="anchor" href="#how-to-phrase-things" aria-label="link to this section">#</a></h2>
<p><strong>Distinguish blocking from non-blocking.</strong> A convention that removes an enormous amount of ambiguity:</p>
<div class="code"><pre><code>blocking: this deletes rows without the tenant filter
suggestion: this could use the existing helper in utils/dates
question: is the retry here intentional given the caller already retries?
nit: typo in the comment
praise: nice — this handles the empty case correctly, which the old one did not</code></pre></div>
<p>The author knows exactly what to act on. The reviewer can leave a thought without implying it must be addressed. Both people save time.</p>
<p><strong>Ask rather than assert when you are unsure.</strong> "Why does this need a lock?" is better than "this does not need a lock" when you are not certain, and it is better even when you are, because the answer might teach you something about the system.</p>
<p><strong>Explain the why, not just the what.</strong> "Use a set here" is an instruction. "Use a set here — this is O(n²) on a list that can have thousands of entries" is teaching, and the author will apply it next time without being told.</p>
<p><strong>Comment on the code, not the person.</strong> "This is confusing" rather than "you wrote this confusingly." Small difference, and it consistently changes how the comment is received.</p>
<h2 id="the-process-problems-that-comments-cannot-fix">the process problems that comments cannot fix<a class="anchor" href="#the-process-problems-that-comments-cannot-fix" aria-label="link to this section">#</a></h2>
<p><strong>The diff is too large.</strong> Past a few hundred lines, review quality collapses. The comment "this PR is too large, can you split it" is the most valuable one available and it is socially costly to write, which is why nobody does.</p>
<p>Make it a policy so it is not a personal judgment.</p>
<p><strong>Review is too late.</strong> If a fundamental problem is found in review, the process failed earlier. That belongs in design.</p>
<p><strong>Only one person reviews.</strong> Different reviewers see different things. For anything significant, two, with different backgrounds.</p>
<p><strong>Comments arrive over three days.</strong> The author has moved on and has to reload the entire context. Batch your review into one pass, and do it within a day.</p>
<h2 id="the-goal">the goal<a class="anchor" href="#the-goal" aria-label="link to this section">#</a></h2>
<p>The purpose of code review is not to find bugs — tests find bugs more reliably and more cheaply.</p>
<p>It is to spread understanding, maintain coherence, and catch the class of problem that automation cannot see: wrong abstraction, wrong layer, missing case, code that will confuse the next reader.</p>
<p>Comments that serve those are worth writing. Everything else is noise with a notification attached.</p>]]></content:encoded></item><item><title>Naming things: the actual rules</title><link>https://readme.news/naming-things-the-actual-rules/</link><guid isPermaLink="true">https://readme.news/naming-things-the-actual-rules/</guid><pubDate>Fri, 01 May 2026 09:00:00 +0000</pubDate><description>It is one of the two hard problems and it is treated as a matter of taste. It isn&#x27;t. Here are the rules that hold.</description><content:encoded><![CDATA[<p>Naming gets treated as bikeshedding — a matter of taste that reasonable people disagree about and that is not worth arguing over.</p>
<p>It is not taste. A name is the <a class="xref" href="/compression-is-underrated/" title="Compression is underrated">compression</a> of everything the reader needs to know about a thing, and a bad one costs every future reader time. There are rules, they are learnable, and most codebases violate them consistently.</p>
<h2 id="the-rules">the rules<a class="anchor" href="#the-rules" aria-label="link to this section">#</a></h2>
<p><strong>1. Length should be proportional to scope.</strong></p>
<p>A loop index used on the next line can be <code>i</code>. A module-level constant read by forty files needs a full sentence of a name.</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python">for i, x in enumerate(items):     # fine, two lines of scope
    ...

MAX_UPLOAD_SIZE_BYTES = 10 * 1024 * 1024   # not MAX_SIZE</code></pre></div>
<p>The inverse error is as common: <code>for currentItemIndex in ...</code> in a three-line loop is noise.</p>
<p><strong>2. Name what it is, not what it does.</strong></p>
<p><code>getUserData</code> and <code>fetchUserData</code> and <code>loadUserData</code> all mean the same thing, and the differences imply distinctions you probably do not intend. Pick a convention — <code>get</code> for cheap and local, <code>fetch</code> for network, <code>load</code> for disk — and hold it.</p>
<p><strong>3. Booleans read as assertions.</strong></p>
<p><code>isValid</code>, <code>hasPermission</code>, <code>canRetry</code>, <code>shouldRefresh</code>. Never a negative: <code>isNotReady</code> produces <code>if not isNotReady</code>, which no human parses correctly on the first read.</p>
<p><strong>4. No abbreviations except universal ones.</strong></p>
<p><code>id</code>, <code>url</code>, <code>http</code>, <code>db</code>, <code>max</code>, <code>min</code> are fine. <code>usr</code>, <code>cfg</code>, <code>mgr</code>, <code>svc</code>, <code>impl</code> are not. You saved four characters and cost every reader a translation step, forever.</p>
<p>The test: would a competent engineer who has never seen this codebase know what it means? If they would have to check, spell it out.</p>
<p><strong>5. Units in the name.</strong></p>
<p><code>timeout</code> is a bug waiting to happen. <code>timeoutMs</code> is not. Same for <code>sizeBytes</code>, <code>distanceMeters</code>, <code>priceCents</code>. Every mixed-unit bug I have ever debugged was preceded by a name that did not say the unit.</p>
<p>This is the highest-value rule on this list and it is the most ignored.</p>
<p><strong>6. Avoid meaningless suffixes.</strong></p>
<p><code>UserManager</code>, <code>DataProcessor</code>, <code>RequestHandler</code>, <code>ThingService</code>. What does a manager do? Nobody knows, which is why the class has grown to nine hundred lines — there is no name-based pressure to keep it focused.</p>
<p>If you cannot name a class without <code>Manager</code> or <code>Helper</code> or <code>Util</code>, it is probably doing several unrelated things and the naming problem is telling you so.</p>
<p><strong>7. Symmetry.</strong></p>
<p><code>open</code>/<code>close</code>, not <code>open</code>/<code>dispose</code>. <code>start</code>/<code>stop</code>, not <code>start</code>/<code>end</code>. <code>add</code>/<code>remove</code>, not <code>add</code>/<code>delete</code>. A reader who learns half your vocabulary should be able to guess the other half.</p>
<p><strong>8. The name should not lie.</strong></p>
<p>The worst possible name is one that was accurate and is not anymore. <code>validateEmail</code> that also normalizes and saves. <code>getUser</code> that creates the user if absent. These are worse than no name, because the reader trusts them and does not check.</p>
<p>When a function's behavior changes, changing its name is part of the work.</p>
<h2 id="the-tell">the tell<a class="anchor" href="#the-tell" aria-label="link to this section">#</a></h2>
<p>Here is the diagnostic that turns naming from taste into information:</p>
<p><strong>If you cannot name it clearly, the thing is probably wrong.</strong></p>
<ul><li>A function you cannot name without "and" is doing two things.</li><li>A variable you cannot name specifically holds something too vague to be useful.</li><li>A class you can only name <code>Manager</code> has no coherent responsibility.</li><li>A boolean parameter you cannot name is a sign the function should be two functions.</li></ul>
<p>The naming difficulty is a signal about the design, and it arrives at the exact moment when the design is cheapest to change.</p>
<h2 id="the-domain-language-rule">the domain language rule<a class="anchor" href="#the-domain-language-rule" aria-label="link to this section">#</a></h2>
<p>Use the words your users and your business use, exactly, and use them consistently.</p>
<p>If the business says "subscription," do not call it <code>plan</code> in one module and <code>membership</code> in another. Every translation between the domain language and the code is a place where a misunderstanding hides, and there will be several.</p>
<p>The corollary: if the business uses one word for two things, or two words for one thing, you have found a domain modeling problem that predates your code. That is worth surfacing.</p>
<h2 id="on-renaming">on renaming<a class="anchor" href="#on-renaming" aria-label="link to this section">#</a></h2>
<p>Modern tooling makes renaming safe and cheap in a typed language. Do it. A name that is 20% better, applied across a module, is a real and permanent improvement to every future reading.</p>
<p>The objection is git blame noise, which is a real cost and a small one. <code>git log --follow</code> and blame-ignore files handle most of it.</p>
<h2 id="the-one-line-version">the one-line version<a class="anchor" href="#the-one-line-version" aria-label="link to this section">#</a></h2>
<p>Say what it is, include the unit, do not abbreviate, do not lie, and if you cannot name it, fix the thing rather than the name.</p>]]></content:encoded></item><item><title>Reading code is a skill and nobody teaches it</title><link>https://readme.news/reading-code-is-a-skill-and-nobody-teaches-it/</link><guid isPermaLink="true">https://readme.news/reading-code-is-a-skill-and-nobody-teaches-it/</guid><pubDate>Tue, 13 Jan 2026 09:00:00 +0000</pubDate><description>We spend years learning to write and approximately zero hours learning to read. Then we spend our careers reading.</description><content:encoded><![CDATA[<p>Every programming course teaches you to write code. Not one of them teaches you to read it.</p>
<p>Then you get a job and spend, conservatively, eighty percent of your time reading: reading the existing system before you change it, reading a pull request, reading a library's source because the docs are wrong, reading a stack trace, reading a diff a machine produced.</p>
<p>The skill you were never taught is the one you use most. And it is a skill — it is learnable, it has technique, and people are dramatically better or worse at it in ways that have little to do with how well they write.</p>
<h2 id="what-good-readers-actually-do">what good readers actually do<a class="anchor" href="#what-good-readers-actually-do" aria-label="link to this section">#</a></h2>
<p>I have watched a lot of people read unfamiliar code. The good ones have a method.</p>
<p><strong>They find the entry points first.</strong> Not the top of the file. The place where control enters the system: <code>main</code>, the route handlers, the message consumers, the event listeners. Everything else is reachable from there and unreachable code is noise.</p>
<p><strong>They read for shape before detail.</strong> A first pass that answers: how many layers? Where is the state? What talks to what? They will read a hundred files shallowly before reading one deeply, and they resist the urge to understand any single function completely on the first pass.</p>
<p><strong>They follow the data, not the calls.</strong> Where does this value come from, where does it go, who mutates it. Call graphs tell you the structure; data flow tells you the behavior, and bugs live in behavior.</p>
<p><strong>They run it.</strong> Reading static text is a bad way to understand a dynamic system. Set a breakpoint, add a print, run the test with a debugger attached. Five minutes of watching it execute beats an hour of reading.</p>
<p><strong>They read the tests first</strong>, when the tests are good. Tests are the spec written in a language the machine checks. A well-tested module's test file is the best available documentation of what it is supposed to do.</p>
<p><strong>They read the history.</strong> <code>git log -S</code> for the function name. When was this added, what was the commit message, what else changed with it. The rationale for a weird piece of code is frequently in a commit from 2019, and nobody looks.</p>
<p><strong>They accept partial understanding.</strong> The bad habit is trying to understand everything before touching anything. You cannot. Understand enough to make the change safely, make it, and let the tests and the review catch what you missed.</p>
<h2 id="the-techniques-worth-practicing-deliberately">the techniques worth practicing deliberately<a class="anchor" href="#the-techniques-worth-practicing-deliberately" aria-label="link to this section">#</a></h2>
<p><strong>Read a library you depend on.</strong> Pick something in your stack, one you use daily and have never opened. Read its source for an hour. You will learn more about your language's idioms than from any tutorial, and you will find at least one thing that changes how you use it.</p>
<p><strong>Explain it to someone.</strong> The fastest way to discover you did not understand something is to try to say it out loud. This is why rubber-duck debugging works and it works just as well for comprehension as for debugging.</p>
<p><strong>Reconstruct the design decision.</strong> For any piece of code that looks strange, ask: what problem would make this the right answer? Usually there was one. Code that looks stupid is either a workaround for something you cannot see, or a fossil of a requirement that no longer exists. Both are worth knowing.</p>
<p><strong>Diff two versions of something.</strong> Reading the evolution of a file teaches you what the author learned. It is a much richer signal than the current state.</p>
<h2 id="why-this-matters-more-now">why this matters more now<a class="anchor" href="#why-this-matters-more-now" aria-label="link to this section">#</a></h2>
<p>Because reading is now a much larger fraction of the job.</p>
<p>If a machine writes a substantial share of the code that lands in your repository, then your value-add is on the reading side of the transaction. The person who can read a 400-line diff and spot the one place that breaks under concurrency is worth enormously more than the person who can produce 400 lines.</p>
<p>And it compounds badly in the other direction: someone who never developed reading skill, in an environment where they write less code than any previous generation, will develop neither.</p>
<h2 id="the-thing-i-would-change-about-how-we-teach">the thing I would change about how we teach<a class="anchor" href="#the-thing-i-would-change-about-how-we-teach" aria-label="link to this section">#</a></h2>
<p>Give students a real codebase and a bug report before you give them a blank file.</p>
<p>Have them find it. Have them explain what the system does. Have them make a one-line fix and defend why it is correct.</p>
<p>That is the actual job. We spend four years preparing people for the part of it that machines now do well, and zero hours on the part that they do not.</p>]]></content:encoded></item><item><title>The pull request is the wrong unit of review</title><link>https://readme.news/the-pull-request-is-the-wrong-unit-of-review/</link><guid isPermaLink="true">https://readme.news/the-pull-request-is-the-wrong-unit-of-review/</guid><pubDate>Thu, 02 Oct 2025 09:00:00 +0000</pubDate><description>It was designed for occasional contributions from strangers. We use it for everything, including code a machine wrote.</description><content:encoded><![CDATA[<p>The pull request was invented to solve a specific problem: someone you do not know and cannot talk to wants to contribute to your project, and you need a way to evaluate their change before it touches your code.</p>
<p>For that problem it is excellent. For the problem most teams actually have — a colleague you sit next to made a change to a system you both understand — it is a poor fit that we have collectively stopped noticing.</p>
<h2 id="what-it-optimizes-for">what it optimizes for<a class="anchor" href="#what-it-optimizes-for" aria-label="link to this section">#</a></h2>
<p><strong>Asynchrony.</strong> Nobody has to be available at the same time. Enormously valuable across <a class="xref" href="/time-zones-and-why-your-calendar-code-is-wrong/" title="Time zones, and why your calendar code is wrong">time zones</a>, and a real cost when the two people are online simultaneously and could have resolved it in four minutes.</p>
<p><strong>A permanent record.</strong> Genuinely useful and undervalued. The PR discussion is frequently the only place a design decision's rationale is written down.</p>
<p><strong>Gatekeeping.</strong> The change cannot land without approval. Necessary for untrusted contributors, and a substantial tax when applied uniformly to trusted ones.</p>
<h2 id="what-it-costs">what it costs<a class="anchor" href="#what-it-costs" aria-label="link to this section">#</a></h2>
<p><strong>Latency.</strong> The median PR spends most of its life waiting. Not being reviewed — waiting to be reviewed. That latency is the single largest contributor to cycle time in most organizations, and it is almost entirely queueing rather than work.</p>
<p><strong>Batching.</strong> Because review has fixed overhead, people batch changes to amortize it. Bigger PRs are exponentially harder to review well, which produces worse review, which produces more defects. Everyone knows this and everyone does it anyway, because the incentive is structural.</p>
<p><strong>Performative approval.</strong> Review quality falls off a cliff past a few hundred lines. Beyond that, "LGTM" means "I skimmed it and I trust you," which is sometimes a fine thing to mean and is not what the approval button claims.</p>
<p><strong>Ownership diffusion.</strong> "It was reviewed" becomes a substitute for "someone understood it." Two people who each 60% understand a change do not add up to one person who understands it.</p>
<h2 id="the-agent-problem">the agent problem<a class="anchor" href="#the-agent-problem" aria-label="link to this section">#</a></h2>
<p>This is the part that has changed and why the framing needs revisiting now.</p>
<p>If an agent produces five pull requests in an hour and a human can meaningfully review two, you have not increased throughput. You have created a queue with a human at the front of it, and the pressure to approve faster is enormous.</p>
<p>The failure mode is predictable: review quality degrades to a rubber stamp, because the alternative is being the bottleneck that everyone can see. The metrics look great. The defect rate does not, six months later.</p>
<p>We do not have a good answer to this yet. Some things that seem to help:</p>
<ul><li><strong>Require the agent to explain its reasoning in the PR body</strong>, specifically the decisions it made and the alternatives it rejected. Reviewing a decision is faster than reconstructing it from a diff.</li><li><strong>Make tests the primary gate and review the secondary one.</strong> If the test suite is strong, review can focus on design rather than correctness.</li><li><strong>Review the <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a>, not the implementation</strong>, for agent-generated code. The interface is where the expensive mistakes live.</li><li><strong>Reject on size.</strong> A machine can produce a 2,000-line diff effortlessly. Say no. It is not a favor to accept it.</li></ul>
<h2 id="the-alternatives-worth-trying">the alternatives worth trying<a class="anchor" href="#the-alternatives-worth-trying" aria-label="link to this section">#</a></h2>
<p><strong>Pair or ensemble programming.</strong> Review happens continuously, at the moment of writing, by someone with full context. Zero latency, better knowledge transfer, and no queue. The cost is synchronous time, which is real, and which teams consistently overestimate relative to the cost of PR latency.</p>
<p><strong>Trunk-based with post-commit review.</strong> Land it, review it after, revert if wrong. Requires strong tests, a fast revert path, and actual trust. Works extremely well where those exist and is a disaster where they do not.</p>
<p><strong>Tiered review by risk.</strong> Not every change needs the same process. A typo fix in a comment and a change to the authentication path are both "a PR" today, and treating them identically is how the process becomes theater.</p>
<h2 id="what-i-actually-recommend">what I actually recommend<a class="anchor" href="#what-i-actually-recommend" aria-label="link to this section">#</a></h2>
<p>Keep pull requests. Change three things:</p>
<ol><li><strong>Cap the size.</strong> A hard limit, enforced by tooling, with an override that requires a written justification. 400 lines is a reasonable line.</li><li><strong>Set a review SLA and measure it.</strong> Not "we should review quickly." A number, on a <a class="xref" href="/the-dashboard-nobody-looks-at/" title="The dashboard nobody looks at">dashboard</a>, reviewed in the same meeting as uptime.</li><li><strong>Make small PRs cheap.</strong> If opening a PR takes ten minutes of ceremony, people will batch. Automate the ceremony.</li></ol>
<p>The tool is fine. The way it is applied to every change regardless of size, risk, or author is the problem, and that is a policy decision, not a technology one.</p>]]></content:encoded></item>
</channel>
</rss>
