Skip to main content
ThunderLang
← All articles
system-design

Requirements as Code: The Real Tradeoffs Nobody Mentions

6 min read · 2026-08-17 · Allen Codewell

Most teams that adopt requirements-as-code spend three times as long writing the specifications as they do writing the feature. That is not a reason to abandon the practice. It is a reason to be honest about what you are actually signing up for.

Requirements as code means encoding what software must do as executable, checkable artifacts rather than prose documents. The form varies: property-based tests in Hypothesis, formal contracts in TLA+, behavior specs in Gherkin, or structured intent declarations like the ones ThunderLang uses to gate AI-generated diffs. The promise is real: your requirements become something a machine can verify, not just a document a human can misread. The problem is that the promise obscures three genuine costs nobody puts on the slide deck.

The Costs That Do Not Show Up in the Demo

The first cost is specification debt. Every executable spec is a bet that the requirement is stable enough to be worth encoding. In practice, requirements shift faster than most teams encode them. A startup changing its pricing model three times in a quarter does not have a formal-methods problem, it has a discovery problem. Encoding unstable requirements into runnable assertions just means you have runnable assertions that are wrong. Specification debt is quieter than technical debt but compounds the same way.

The second cost is the expertise gradient. Writing a good property-based test for a distributed system is not junior work. TLA+ has a learning curve that measured in one study at roughly 40 hours before engineers could write non-trivial specs without significant coaching. Most teams read that and think their engineers are faster. Some are. Most are not. The practical consequence is that specifications get written by one or two people and become a single-point-of-knowledge problem that is worse than the documentation problem you were trying to solve.

The third cost is false confidence.

A passing spec suite tells you the system satisfies the properties you thought to encode. It says nothing about the properties you did not think of. This is not a theoretical concern. The AWS S3 outage in 2017 was not a violation of any spec the team had written. The specification was complete with respect to known invariants and silent on emergent load behavior. Teams that have invested heavily in executable specs sometimes under-invest in exploratory testing and chaos engineering precisely because the green suite provides psychological cover.

None of these costs mean you should not do it. They mean you should do it with clear eyes.

Where the Tradeoff Still Wins

The tradeoff flips in your favor under two conditions: when requirements are stable and when the cost of a wrong change is high.

Stable requirements are more common than they appear. Authentication flows, billing invariants, data retention rules, API contracts with external consumers, compliance obligations. These do not change on product whims. Encoding them as executable specifications pays compound interest: every future change gets validated against them automatically, and you stop paying the human review cost repeatedly for the same constraint.

High cost of a wrong change is the other lever. If a bad deploy costs you two engineers a weekend, informal review is probably fine. If it costs you a SOC 2 audit finding, a customer contract penalty, or a safety incident, the economics shift dramatically. This is why formal methods took root in aviation and finance before they appeared in consumer software. The cost asymmetry justifies the upfront investment.

This is also where AI-assisted development changes the calculus in a specific way. When an LLM is writing or modifying code, the gap between what the engineer intended and what the model produced is harder to catch in review than the gap between two human-written implementations. The model's output looks fluent and plausible. It is more likely to satisfy surface-level review and violate a deep invariant. Executable requirements become a verification layer that is structurally hard to skip, not a best-practice document that gets read once and ignored.

ThunderLang is built on exactly this premise: declare intent as structured, checkable constraints, then run every AI-generated diff against them before merge. The getting started docs show how to encode requirements at the level that actually matters, not at the level that is easy to write. That distinction matters because easy-to-write specs tend to test the happy path twice in different syntax.

The strongest counter-argument to requirements as code is that good engineers do not need it. Experienced engineers catch invariant violations in review. True, on small stable teams with low turnover. Not true on teams that are growing, rotating, or shipping AI-generated code at volume. The argument for informal review is an argument for a specific team shape that most teams do not have most of the time.

One more thing worth naming: the teams that get the most from executable requirements are not the ones who try to specify everything. They are the ones who are ruthless about what goes in. Three well-chosen invariants that actually constrain the system are worth more than thirty behavioral specs that describe what the code already does. Specification scope discipline is a senior-engineering skill, and it is not discussed anywhere near enough.

If you are starting from zero, pick the five requirements in your system that are both stable and expensive to violate. Encode those. Run them on every merge. Measure how often they catch something that review missed. You will have your own data within a quarter, and that data will tell you how far to push it.

Gate your first AI change

If you are shipping AI-assisted code today, ThunderLang gives you a concrete place to start: declare what a change must satisfy, then gate AI-written code against that intent with a verify-diff and durable proof artifacts. Try it here.

The point of requirements as code is not to write more artifacts, it is to make the artifacts you write do work that humans cannot reliably do at scale.