{"course":{"slug":"how-software-changed","title":"How Software Development Changed","url":"https://addaly.com/learn/how-software-changed","level":"intermediate","track":"work"},"lesson":{"slug":"specs-that-do-not-rot","title":"Keeping the written thing true","url":"https://addaly.com/learn/how-software-changed/specs-that-do-not-rot","position":38,"of":87,"minutes":8,"answer":"Written specifications stay true through two mechanisms only (living in the same commit as the code and being checked by something that fails) so make what you can executable, keep decisions immutable.","keyIdea":"Written specifications stay true through two mechanisms only (living in the same commit as the code and being checked by something that fails) so make what you can executable, keep decisions immutable, and delete prose you are not maintaining."},"body":{"format":"markdown","text":"## A wrong document is worse than none\n\nThis is the objection that killed heavy specification in the 1990s and it was correct. A document that describes a system that no longer exists does not merely fail to help; it misleads with authority. Someone reads it, believes it, and builds on a false premise.\n\nSo any argument for writing more must come with a mechanism for keeping it true. Otherwise you are recommending a liability.\n\nThere are exactly two mechanisms that work: **put it where the code is**, and **make something check it**.\n\n## Mechanism one: same repository, same commit\n\nThe specification lives in the repository, next to the code it describes, and changes in the same pull request. Not a wiki. Not a ticketing system. Not a shared drive with a folder called Specs.\n\nThe reason is behavioural rather than technical. Updating a file that is already open in your editor and already in your diff costs about fifteen seconds. Opening a separate tool, finding the page, editing it and saving is a separate act of will, performed at the end of a task, when you are tired and it is Friday. The second one does not happen, reliably, on every team, for ever.\n\nA simple layout:\n\n```\nbilling/\n  fx.py\n  fx_test.py\n  README.md          # what this module is for, and its decisions\ndocs/adr/\n  0009-rate-limiter-availability.md\n  0014-store-converted-amounts.md\n```\n\nAnd a review norm: a change to behaviour that does not touch the nearby prose gets a question in review. Not a block, a question. \"Does the README still describe this?\" is usually answered in ten seconds and occasionally catches something significant.\n\n## Mechanism two: make it executable where you can\n\nProse rots because nothing objects when it becomes false. Anything checked by a machine cannot.\n\n**Doctests.** The example in the documentation is run as a test.\n\n```python\ndef to_minor(amount: str, currency: str) -> int:\n    \"\"\"Convert a decimal string to minor units.\n\n    >>> to_minor(\"12.50\", \"USD\")\n    1250\n    >>> to_minor(\"12.500\", \"KWD\")     # 3 decimal places\n    12500\n    >>> to_minor(\"1200\", \"JPY\")       # no minor unit\n    1200\n    \"\"\"\n```\n\nThose three lines are documentation, specification and test simultaneously, and the KWD and JPY cases encode two facts about the world that a reader would otherwise not know. If somebody breaks the currency-exponent handling, the documentation fails.\n\n**Acceptance criteria as test names**, from earlier in this module. `pytest --collect-only` gives you the specification back, and it is verified on every commit.\n\n**Schemas and contracts.** An OpenAPI document that generates the client, a protobuf file that generates both sides, a JSON Schema validated in a test. Anything generated from a specification cannot drift from it, because drift is a build failure.\n\n**Links from code to prose.** A comment naming a decision record means the code and the record are found together, and a reader who checks the link notices when it is stale.\n\n## What rots, and should\n\nBe realistic about the categories.\n\n- **Decisions do not rot.** Why you chose fail-open in August 2026 is a fact about August 2026 and remains true for ever. This is why decision records are immutable and superseded rather than edited.\n- **Descriptions of current behaviour rot fast.** Keep them short, keep them near the code, and prefer generating them where you can.\n- **Tutorials and getting-started guides rot fastest of all**, because they contain commands, versions and paths. If you have one, run it in CI: a script that follows your own quickstart on a clean machine is worth more than any amount of careful writing.\n- **Diagrams rot silently and completely.** A diagram in an image file, drawn once, is wrong within a year and nobody will ever know. Diagrams written as text (Mermaid, Graphviz, PlantUML, all free) at least appear in diffs, which gives them a chance.\n\n## The deletion rule\n\nThe last piece, and the one that keeps the whole thing honest: **delete documentation you are not maintaining.**\n\nWhen you find a page describing a service decommissioned two years ago, do not leave it because it might be useful. Delete it. It is in git if anyone needs it, and its presence costs everyone who reads it looking for something true.\n\nThe test to apply to any document: *if this were wrong, would anybody find out?* If the answer is no, either wire up something that would find out, or accept that it is a note rather than a specification, and file it accordingly."},"check":{"question":"A team keeps detailed specifications in a wiki, reviewed quarterly. Within a year most pages describe behaviour the system no longer has. Which change would most reliably fix this?","options":[{"text":"Increase the review cadence to monthly and assign each page a named owner responsible for its accuracy.","correct":false,"tempting":"Adds effort against the same structural problem; a periodic review still happens after drift rather than preventing it."},{"text":"Move the specifications into the repository so they appear in the same diff as the code they describe.","correct":true},{"text":"Replace the prose with diagrams, which are faster to update and communicate structure more efficiently.","correct":false,"tempting":"Diagrams drift more silently than prose, especially as image files that never appear in a diff."},{"text":"Generate the specification pages automatically from code comments, so they always reflect the current implementation.","correct":false,"tempting":"Solves drift by removing intent: generated descriptions restate what the code does, which is the part a reader could already see."}]},"prev":{"slug":"examples-beat-adjectives","title":"Examples beat adjectives","url":"https://addaly.com/learn/how-software-changed/examples-beat-adjectives"},"next":{"slug":"when-not-to-specify","title":"When writing it down is the wrong move","url":"https://addaly.com/learn/how-software-changed/when-not-to-specify"},"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) 'Keeping the written thing true', in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/specs-that-do-not-rot (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":"Keeping the written thing true","courseTitle":"How Software Development Changed","module":{"index":4,"slug":"saying-what-you-want","title":"Saying what you want"},"answer":null,"keyIdea":"Written specifications stay true through two mechanisms only (living in the same commit as the code and being checked by something that fails) so make what you can executable, keep decisions immutable, and delete prose you are not maintaining.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"a-wrong-document-is-worse-than-none","runs":[{"text":"A wrong document is worse than none"}]},{"t":"p","runs":[{"text":"This is the objection that killed heavy specification in the 1990s and it was correct. A document that describes a system that no longer exists does not merely fail to help; it misleads with authority. Someone reads it, believes it, and builds on a false premise."}]},{"t":"p","runs":[{"text":"So any argument for writing more must come with a mechanism for keeping it true. Otherwise you are recommending a liability."}]},{"t":"p","runs":[{"text":"There are exactly two mechanisms that work: "},{"text":"put it where the code is","b":true},{"text":", and "},{"text":"make something check it","b":true},{"text":"."}]},{"t":"h2","id":"mechanism-one-same-repository-same-commit","runs":[{"text":"Mechanism one: same repository, same commit"}]},{"t":"p","runs":[{"text":"The specification lives in the repository, next to the code it describes, and changes in the same pull request. Not a wiki. Not a ticketing system. Not a shared drive with a folder called Specs."}]},{"t":"p","runs":[{"text":"The reason is behavioural rather than technical. Updating a file that is already open in your editor and already in your diff costs about fifteen seconds. Opening a separate tool, finding the page, editing it and saving is a separate act of will, performed at the end of a task, when you are tired and it is Friday. The second one does not happen, reliably, on every team, for ever."}]},{"t":"p","runs":[{"text":"A simple layout:"}]},{"t":"code","lang":"","label":"Text","text":"billing/\n  fx.py\n  fx_test.py\n  README.md          # what this module is for, and its decisions\ndocs/adr/\n  0009-rate-limiter-availability.md\n  0014-store-converted-amounts.md"},{"t":"p","runs":[{"text":"And a review norm: a change to behaviour that does not touch the nearby prose gets a question in review. Not a block, a question. “Does the README still describe this?” is usually answered in ten seconds and occasionally catches something significant."}]},{"t":"h2","id":"mechanism-two-make-it-executable-where-you-can","runs":[{"text":"Mechanism two: make it executable where you can"}]},{"t":"p","runs":[{"text":"Prose rots because nothing objects when it becomes false. Anything checked by a machine cannot."}]},{"t":"p","runs":[{"text":"Doctests.","b":true},{"text":" The example in the documentation is run as a test."}]},{"t":"code","lang":"python","label":"Python","text":"def to_minor(amount: str, currency: str) -> int:\n    \"\"\"Convert a decimal string to minor units.\n\n    >>> to_minor(\"12.50\", \"USD\")\n    1250\n    >>> to_minor(\"12.500\", \"KWD\")     # 3 decimal places\n    12500\n    >>> to_minor(\"1200\", \"JPY\")       # no minor unit\n    1200\n    \"\"\""},{"t":"p","runs":[{"text":"Those three lines are documentation, specification and test simultaneously, and the KWD and JPY cases encode two facts about the world that a reader would otherwise not know. If somebody breaks the currency-exponent handling, the documentation fails."}]},{"t":"p","runs":[{"text":"Acceptance criteria as test names","b":true},{"text":", from earlier in this module. "},{"text":"pytest --collect-only","code":true},{"text":" gives you the specification back, and it is verified on every commit."}]},{"t":"p","runs":[{"text":"Schemas and contracts.","b":true},{"text":" An OpenAPI document that generates the client, a protobuf file that generates both sides, a JSON Schema validated in a test. Anything generated from a specification cannot drift from it, because drift is a build failure."}]},{"t":"p","runs":[{"text":"Links from code to prose.","b":true},{"text":" A comment naming a decision record means the code and the record are found together, and a reader who checks the link notices when it is stale."}]},{"t":"h2","id":"what-rots-and-should","runs":[{"text":"What rots, and should"}]},{"t":"p","runs":[{"text":"Be realistic about the categories."}]},{"t":"ul","items":[[{"text":"Decisions do not rot.","b":true},{"text":" Why you chose fail-open in August 2026 is a fact about August 2026 and remains true for ever. This is why decision records are immutable and superseded rather than edited."}],[{"text":"Descriptions of current behaviour rot fast.","b":true},{"text":" Keep them short, keep them near the code, and prefer generating them where you can."}],[{"text":"Tutorials and getting-started guides rot fastest of all","b":true},{"text":", because they contain commands, versions and paths. If you have one, run it in CI: a script that follows your own quickstart on a clean machine is worth more than any amount of careful writing."}],[{"text":"Diagrams rot silently and completely.","b":true},{"text":" A diagram in an image file, drawn once, is wrong within a year and nobody will ever know. Diagrams written as text (Mermaid, Graphviz, PlantUML, all free) at least appear in diffs, which gives them a chance."}]]},{"t":"h2","id":"the-deletion-rule","runs":[{"text":"The deletion rule"}]},{"t":"p","runs":[{"text":"The last piece, and the one that keeps the whole thing honest: "},{"text":"delete documentation you are not maintaining.","b":true}]},{"t":"p","runs":[{"text":"When you find a page describing a service decommissioned two years ago, do not leave it because it might be useful. Delete it. It is in git if anyone needs it, and its presence costs everyone who reads it looking for something true."}]},{"t":"p","runs":[{"text":"The test to apply to any document: "},{"text":"if this were wrong, would anybody find out?","i":true},{"text":" If the answer is no, either wire up something that would find out, or accept that it is a note rather than a specification, and file it accordingly."}]}],"check":{"question":[{"text":"A team keeps detailed specifications in a wiki, reviewed quarterly. Within a year most pages describe behaviour the system no longer has. Which change would most reliably fix this?"}],"options":[{"text":[{"text":"Increase the review cadence to monthly and assign each page a named owner responsible for its accuracy."}],"correct":false,"tempting":[{"text":"Adds effort against the same structural problem; a periodic review still happens after drift rather than preventing it."}]},{"text":[{"text":"Move the specifications into the repository so they appear in the same diff as the code they describe."}],"correct":true,"tempting":null},{"text":[{"text":"Replace the prose with diagrams, which are faster to update and communicate structure more efficiently."}],"correct":false,"tempting":[{"text":"Diagrams drift more silently than prose, especially as image files that never appear in a diff."}]},{"text":[{"text":"Generate the specification pages automatically from code comments, so they always reflect the current implementation."}],"correct":false,"tempting":[{"text":"Solves drift by removing intent: generated descriptions restate what the code does, which is the part a reader could already see."}]}],"order":[2,1,0,3],"why":null},"prev":{"slug":"examples-beat-adjectives","title":"Examples beat adjectives","minutes":8},"next":{"slug":"when-not-to-specify","title":"When writing it down is the wrong move","minutes":8}}}