{"course":{"slug":"how-software-changed","title":"How Software Development Changed","url":"https://addaly.com/learn/how-software-changed","level":"intermediate","track":"work"},"lesson":{"slug":"the-review-budget","title":"Spending a review budget","url":"https://addaly.com/learn/how-software-changed/the-review-budget","position":13,"of":87,"minutes":9,"answer":"Review is a budget, so allocate it by what each file can cost rather than evenly across the diff.","keyIdea":"Review is a budget, so allocate it by what each file can cost rather than evenly across the diff, and record what you actually read so an approval carries a specific meaning rather than an implied one."},"body":{"format":"markdown","text":"## You have forty minutes, and the diff has 700 lines\n\nThis is now the normal situation, and the normal response (start at the top, read until tired, approve) is the worst available allocation. Review is a scarce resource being spent, and spending it evenly across a diff means spending most of it on the parts that could not hurt you.\n\nWhat follows is a triage procedure. It is not more rigorous than reading everything. It is what you do because you cannot.\n\n## Sort the files by what they can cost\n\nBefore reading a line, open the file list and put every file into one of three tiers.\n\n**Tier 1: read every line, twice.** Anything touching money, authentication, authorisation, deletion, personal data, or another company's system. Anything that writes to a database without a transaction. Anything that sends a message to a human. Migrations. Configuration that differs between environments. Concurrency of any kind.\n\n**Tier 2: read carefully once, and check the boundaries.** Business logic, request handlers, anything with a conditional in it, anything that transforms data.\n\n**Tier 3: skim for shape.** Tests, fixtures, formatting, generated clients, documentation, translations, lockfile churn.\n\n> Figure: Forty minutes, spent by what a file can cost. Layers, top to bottom: Tier 1, read every line twice (Money, authentication, permissions, deletion, personal data, another company's system, migrations, concurrency. About 80 lines of a 700-line diff, and thirty of your forty minutes.); Tier 2, read once and check the boundaries (Business logic, request handlers, anything with a conditional in it, anything that transforms data. About 150 lines.); Tier 3, skim for shape (Tests, fixtures, formatting, generated clients, translations, lockfile churn. Then say in the review that you skimmed it.). Spending review evenly across a diff spends most of it on the parts that could not hurt you. Saying which part you actually read is the half that makes an approval mean something.\n\nA typical 700-line diff is 80 lines of tier 1, 150 of tier 2, and the rest tier 3. Spend thirty of your forty minutes on those first 80 lines, and say in the review that you skimmed the rest. Saying so is the important half: an approval that silently means \"I read a third of this\" is the thing that makes review meaningless as a signal.\n\n## Read in this order\n\n1. **The description and the linked ticket.** What is this supposed to do? If you cannot state that in one sentence before reading code, ask, and stop. Reviewing without knowing the intent means you can only check the change against itself, which is exactly the check that stopped working.\n2. **The migration, if there is one.** It is the only irreversible part.\n3. **The interface changes.** New public functions, changed signatures, new API fields, new configuration keys. These are commitments; the implementation behind them can be fixed next week and the interface cannot.\n4. **Tier 1 files, line by line.**\n5. **The tests: not to check they pass, but to ask what they do not cover.**\n6. **Tier 2, then a skim of tier 3.**\n\n## Three questions that find more than reading does\n\n**\"What did this change that it was not asked to change?\"** Scan the file list against the ticket. A request to fix date formatting that also touches a dependency version, a serialiser and a config default is three changes wearing one hat, and the two you did not expect are the ones nobody has thought about.\n\n**\"What is the worst input for this?\"** Not the wrong input: the worst one. Empty, enormous, duplicated, out of order, arriving twice, arriving from a different timezone, containing a newline. Pick the one that seems most awkward and trace it through by hand.\n\n**\"Why is this line here?\"** Choose two lines that look load-bearing and ask the author, plainly. This costs you thirty seconds and it is the single highest-yield question in review, because it tests whether the change is understood by anybody at all.\n\n## Time-box it, and say when you ran out\n\nSet a timer for forty minutes. When it goes, stop, and write what you actually did:\n\n> Read the migration, `billing/charge.py` and `auth/session.py` line by line. Skimmed the tests and the generated client. Did not review the frontend changes; someone who works in that area should.\n\nThis is not an admission of failure. It is the only thing that makes an approval mean something specific, and it makes the gap visible so somebody can fill it. A team where every approval carries this note is a team that can see its own review coverage.\n\n## When to refuse\n\nRefusing to review is a legitimate and underused action. Do it when the diff is too large to review meaningfully, when it does three things at once, when the description does not say what it is for, or when you do not know the area well enough and someone else does.\n\nThe phrasing that keeps this collegial: \"I can review the API and storage parts properly; I would be rubber-stamping the rest. Can we split it, or can someone from the payments side take those files?\"\n\nThat sentence protects the reviewer, the author and the system, and it is a normal thing to say. Teams where it is not normal do not have fewer unreviewed changes; they have the same number, silently approved."},"check":{"question":"A reviewer with limited time reads every line of a 700-line diff at a uniform pace, finishes, and approves. A colleague reads only the migration, the auth changes and the payment handler, and writes down what they skipped. Why is the second review more useful?","options":[{"text":"It takes less time, so the reviewer has more capacity left for other changes, which relieves the constraint.","correct":false,"tempting":"Optimises the reviewer's throughput rather than the quality of the signal; a faster meaningless review makes the pile move without making it safer."},{"text":"Reading uniformly means each line receives too little attention to catch anything, so the first review found nothing at all.","correct":false,"tempting":"Overstates: uniform reading does catch defects, just fewest of them in the places where a defect is most expensive."},{"text":"It concentrates attention where a defect would be expensive, and its stated scope tells the team exactly which parts remain unreviewed.","correct":true},{"text":"Migrations, authentication and payments are the areas where generated code is most often wrong, so they deserve the attention on statistical grounds.","correct":false,"tempting":"Substitutes a claim about defect frequency for the real criterion, which is the cost of a defect rather than its likelihood."}]},"prev":{"slug":"orienting-in-an-unfamiliar-system","title":"Orienting in a system you have never seen","url":"https://addaly.com/learn/how-software-changed/orienting-in-an-unfamiliar-system"},"next":{"slug":"diff-size-economics","title":"Why the diff has to be small","url":"https://addaly.com/learn/how-software-changed/diff-size-economics"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Spending a review budget', in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/the-review-budget (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Spending a review budget","courseTitle":"How Software Development Changed","module":{"index":2,"slug":"reading-what-you-did-not-write","title":"Reading what you did not write"},"answer":null,"keyIdea":"Review is a budget, so allocate it by what each file can cost rather than evenly across the diff, and record what you actually read so an approval carries a specific meaning rather than an implied one.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"stack","title":"Forty minutes, spent by what a file can cost","layers":[{"label":"Tier 1, read every line twice","detail":"Money, authentication, permissions, deletion, personal data, another company’s system, migrations, concurrency. About 80 lines of a 700-line diff, and thirty of your forty minutes."},{"label":"Tier 2, read once and check the boundaries","detail":"Business logic, request handlers, anything with a conditional in it, anything that transforms data. About 150 lines."},{"label":"Tier 3, skim for shape","detail":"Tests, fixtures, formatting, generated clients, translations, lockfile churn. Then say in the review that you skimmed it."}],"caption":"Spending review evenly across a diff spends most of it on the parts that could not hurt you. Saying which part you actually read is the half that makes an approval mean something."},"alt":"Figure: Forty minutes, spent by what a file can cost. Layers, top to bottom: Tier 1, read every line twice (Money, authentication, permissions, deletion, personal data, another company’s system, migrations, concurrency. About 80 lines of a 700-line diff, and thirty of your forty minutes.); Tier 2, read once and check the boundaries (Business logic, request handlers, anything with a conditional in it, anything that transforms data. About 150 lines.); Tier 3, skim for shape (Tests, fixtures, formatting, generated clients, translations, lockfile churn. Then say in the review that you skimmed it.). Spending review evenly across a diff spends most of it on the parts that could not hurt you. Saying which part you actually read is the half that makes an approval mean something."}},"blocks":[{"t":"h2","id":"you-have-forty-minutes-and-the-diff-has-700-lines","runs":[{"text":"You have forty minutes, and the diff has 700 lines"}]},{"t":"p","runs":[{"text":"This is now the normal situation, and the normal response (start at the top, read until tired, approve) is the worst available allocation. Review is a scarce resource being spent, and spending it evenly across a diff means spending most of it on the parts that could not hurt you."}]},{"t":"p","runs":[{"text":"What follows is a triage procedure. It is not more rigorous than reading everything. It is what you do because you cannot."}]},{"t":"h2","id":"sort-the-files-by-what-they-can-cost","runs":[{"text":"Sort the files by what they can cost"}]},{"t":"p","runs":[{"text":"Before reading a line, open the file list and put every file into one of three tiers."}]},{"t":"p","runs":[{"text":"Tier 1: read every line, twice.","b":true},{"text":" Anything touching money, authentication, authorisation, deletion, personal data, or another company’s system. Anything that writes to a database without a transaction. Anything that sends a message to a human. Migrations. Configuration that differs between environments. Concurrency of any kind."}]},{"t":"p","runs":[{"text":"Tier 2: read carefully once, and check the boundaries.","b":true},{"text":" Business logic, request handlers, anything with a conditional in it, anything that transforms data."}]},{"t":"p","runs":[{"text":"Tier 3: skim for shape.","b":true},{"text":" Tests, fixtures, formatting, generated clients, documentation, translations, lockfile churn."}]},{"t":"p","runs":[{"text":"A typical 700-line diff is 80 lines of tier 1, 150 of tier 2, and the rest tier 3. Spend thirty of your forty minutes on those first 80 lines, and say in the review that you skimmed the rest. Saying so is the important half: an approval that silently means “I read a third of this” is the thing that makes review meaningless as a signal."}]},{"t":"h2","id":"read-in-this-order","runs":[{"text":"Read in this order"}]},{"t":"ol","start":1,"items":[[{"text":"The description and the linked ticket.","b":true},{"text":" What is this supposed to do? If you cannot state that in one sentence before reading code, ask, and stop. Reviewing without knowing the intent means you can only check the change against itself, which is exactly the check that stopped working."}],[{"text":"The migration, if there is one.","b":true},{"text":" It is the only irreversible part."}],[{"text":"The interface changes.","b":true},{"text":" New public functions, changed signatures, new API fields, new configuration keys. These are commitments; the implementation behind them can be fixed next week and the interface cannot."}],[{"text":"Tier 1 files, line by line.","b":true}],[{"text":"The tests: not to check they pass, but to ask what they do not cover.","b":true}],[{"text":"Tier 2, then a skim of tier 3.","b":true}]]},{"t":"h2","id":"three-questions-that-find-more-than-reading-does","runs":[{"text":"Three questions that find more than reading does"}]},{"t":"p","runs":[{"text":"”What did this change that it was not asked to change?”","b":true},{"text":" Scan the file list against the ticket. A request to fix date formatting that also touches a dependency version, a serialiser and a config default is three changes wearing one hat, and the two you did not expect are the ones nobody has thought about."}]},{"t":"p","runs":[{"text":"”What is the worst input for this?”","b":true},{"text":" Not the wrong input: the worst one. Empty, enormous, duplicated, out of order, arriving twice, arriving from a different timezone, containing a newline. Pick the one that seems most awkward and trace it through by hand."}]},{"t":"p","runs":[{"text":"”Why is this line here?”","b":true},{"text":" Choose two lines that look load-bearing and ask the author, plainly. This costs you thirty seconds and it is the single highest-yield question in review, because it tests whether the change is understood by anybody at all."}]},{"t":"h2","id":"time-box-it-and-say-when-you-ran-out","runs":[{"text":"Time-box it, and say when you ran out"}]},{"t":"p","runs":[{"text":"Set a timer for forty minutes. When it goes, stop, and write what you actually did:"}]},{"t":"quote","runs":[{"text":"Read the migration, "},{"text":"billing/charge.py","code":true},{"text":" and "},{"text":"auth/session.py","code":true},{"text":" line by line. Skimmed the tests and the generated client. Did not review the frontend changes; someone who works in that area should."}]},{"t":"p","runs":[{"text":"This is not an admission of failure. It is the only thing that makes an approval mean something specific, and it makes the gap visible so somebody can fill it. A team where every approval carries this note is a team that can see its own review coverage."}]},{"t":"h2","id":"when-to-refuse","runs":[{"text":"When to refuse"}]},{"t":"p","runs":[{"text":"Refusing to review is a legitimate and underused action. Do it when the diff is too large to review meaningfully, when it does three things at once, when the description does not say what it is for, or when you do not know the area well enough and someone else does."}]},{"t":"p","runs":[{"text":"The phrasing that keeps this collegial: “I can review the API and storage parts properly; I would be rubber-stamping the rest. Can we split it, or can someone from the payments side take those files?”"}]},{"t":"p","runs":[{"text":"That sentence protects the reviewer, the author and the system, and it is a normal thing to say. Teams where it is not normal do not have fewer unreviewed changes; they have the same number, silently approved."}]}],"check":{"question":[{"text":"A reviewer with limited time reads every line of a 700-line diff at a uniform pace, finishes, and approves. A colleague reads only the migration, the auth changes and the payment handler, and writes down what they skipped. Why is the second review more useful?"}],"options":[{"text":[{"text":"It takes less time, so the reviewer has more capacity left for other changes, which relieves the constraint."}],"correct":false,"tempting":[{"text":"Optimises the reviewer’s throughput rather than the quality of the signal; a faster meaningless review makes the pile move without making it safer."}]},{"text":[{"text":"Reading uniformly means each line receives too little attention to catch anything, so the first review found nothing at all."}],"correct":false,"tempting":[{"text":"Overstates: uniform reading does catch defects, just fewest of them in the places where a defect is most expensive."}]},{"text":[{"text":"It concentrates attention where a defect would be expensive, and its stated scope tells the team exactly which parts remain unreviewed."}],"correct":true,"tempting":null},{"text":[{"text":"Migrations, authentication and payments are the areas where generated code is most often wrong, so they deserve the attention on statistical grounds."}],"correct":false,"tempting":[{"text":"Substitutes a claim about defect frequency for the real criterion, which is the cost of a defect rather than its likelihood."}]}],"order":[3,0,1,2],"why":null},"prev":{"slug":"orienting-in-an-unfamiliar-system","title":"Orienting in a system you have never seen","minutes":10},"next":{"slug":"diff-size-economics","title":"Why the diff has to be small","minutes":9}}}