The Cap on Simplicity Only Governs What You Can Count

Mechanical caps only hold the floor of code simplicity; the real discipline is asking in review whether an abstraction serves the requirement in front of you.
Mechanical rules like parameter caps, cyclomatic complexity, and function-length limits automatically catch obviously bloated code, and they're a floor worth keeping—but they can't answer the judgment question of "is this even necessary." The article points out that the most insidious bloat is the version that "looks more general": a function that starts from a single use case and lands just inside the parameter limit, with a tidy diff, no rule firing, yet paying for a hypothetical future. Lowering the cap to three doesn't close the gap; it just makes the fourth parameter reappear as an options object or the like. The real discipline is a human asking in code review: does this version serve the requirement in front of me? AI agents amplify the problem further—they treat existing abstractions as given facts and keep extending them, hardening over-engineering into infrastructure.
Title
The Cap on Simplicity Only Governs What You Can Count
Meta Description
Parameter caps, cyclomatic complexity, and function-length limits only govern the countable part of simplicity. This article examines why code that "looks more general" slips past every mechanical rule, and how agents make the problem worse.
Article Content
Code complexity tools can help you count parameters, compute cyclomatic complexity, and cap function length, but they can't govern a more insidious kind of bloat: the version that "looks more general."
Someone agrees on "four parameters max" in a retrospective, and before lunch someone has already broken it. Once the deadline passes, someone adds a parameter for "might need it next time," someone gives a helper a flag so one caller can skip half the logic. Each step looks reasonable on its own, and at no single moment did anyone decide to make the code harder to read.

The Mechanical Half: Worth Doing, and It Works
A three-parameter cap, a cyclomatic complexity ceiling, a hard function-length limit—these mechanical rules are easy to enforce and worth enforcing. A cap enforced by the build system doesn't get sleepy at 5 p.m. and doesn't care whether Friday is a release day. I run all three, and they genuinely catch truly tangled code early.
The value of these tools is consistency. People relax their standards when tired and compromise before deadlines, but CI doesn't. Handing automatable constraints to automation is the low-effort approach.
But the low-effort approach has places it can't reach.
Cyclomatic Complexity was introduced by Thomas McCabe in 1976 and measures the number of independent execution paths through a function. It's calculated as "number of decision points + 1": each if, else if, case, &&, ||, ? :, and loop statement adds a branch path. A function with a cyclomatic complexity of 10 has roughly 10 possible execution paths, and the cost of testing every branch rises accordingly. The industry's common rule of thumb is no more than 10 per function, and anything above 15 is often treated as a signal to refactor. But cyclomatic complexity only counts control-flow branches; it's completely insensitive to data coupling, naming quality, or whether the level of abstraction is appropriate—which is precisely its typical limitation as "countable simplicity."
Function-length limits are usually measured in executable lines of code (excluding blank lines and comments), with common thresholds between 40 and 80 lines. Their value isn't in the number itself but in the fact that length tends to correlate with the number of control-flow branches, the number of local variables, and the cost of understanding. But short functions aren't inherently better: hard-splitting logic into a pile of one- or two-line helpers that get called repeatedly can actually increase the cost of jumping around, forcing readers to shuttle between multiple locations to piece together the full intent. That's why a length limit is only a "floor."
The Cap Has a Gap
The person reviewing the diff can't see the problem. The cap can't see the new code that "was born inside the limit."
An agent is asked to "make this code reusable," and it hands you a version that starts from a single use case and grows straight to four parameters. Four—just under the limit. The function is short. No rule fires, and the diff looks tidy.
This is the kind of change I find hardest to reject, because accepting it feels like good taste rather than the opposite. That more general shape just looks right. The problem is: which concrete requirement does this general version serve? Most of the time, the honest answer is "none for now."
What Requirement Does This Serve?
There's no default answer, so a linter can't ask the question.
In review, the most common honest answer is "not yet." A parameter added for possible future use, a flag that lets one caller skip half the logic, a config option in case someone asks—they're all the same kind of thing: paying for a hypothetical future.
Lowering the parameter cap to three doesn't close the gap either. It just makes the fourth parameter appear under a different name. The name hides the count. For example, packing four parameters into an options object, or splitting them into two "related" functions—the counting rule is satisfied, but the cognitive load hasn't dropped at all.
What the cap can govern is only "countable simplicity." It can't govern the question of "is this even necessary?"
YAGNI (You Aren't Gonna Need It) is one of the core principles of Extreme Programming (XP), championed by Ron Jeffries and others, and literally means "you aren't going to need it." It argues that you shouldn't write code or reserve extension points for a requirement before it actually appears. The counterpart is "design for the future," which stacks abstraction layers on top of uncertainty at the cost of increased complexity in the current code, while the payoff may never materialize.
YAGNI isn't opposed to abstraction; it requires that the timing of abstraction be driven by real requirements. A common heuristic: extract an abstraction only when the second or third real use case appears and a common pattern can be induced. The risk of premature abstraction is that the shape of the abstraction is often designed around hypothetical use cases—once a real use case doesn't match, the abstraction has to be torn down and rebuilt, and all the upfront investment is wasted. The "paying for a hypothetical future" mentioned in the article is exactly the behavior pattern YAGNI aims to curb.
The accompanying practice is to leave a clue for a "generalization starting point"—a comment explaining the boundary of the current implementation and where to start when the requirement expands. That way you keep the minimal implementation today without forcing the next person to guess from scratch.
So What Are Caps Actually For?
None of the above is an argument against caps. They enforce the "countable" part of simplicity. Cyclomatic complexity catches functions that quietly grow fat; length limits catch functions that are hard to read. They're a floor, not a ceiling.
The real discipline is in review, in the room, with a human asking the question: does this version serve the requirement in front of me?
The version I want is: the minimal implementation that serves the current requirement, plus a comment so the next person knows where to start generalizing when the requirement actually arrives. Not an abstraction layer written now for a requirement that doesn't exist yet.
Agents Raise the Stakes
An agent takes names and shapes as given facts and then keeps building on top of them. An accepted general helper becomes the vocabulary for everything that follows.
A human reviewer might at least stop during some review and ask "do we really need this," but an agent won't—what it sees is an abstraction that already exists and has been merged into the codebase, and its default behavior is to reuse and extend, not to question. So that "prepared for the future" parameter becomes infrastructure.
That's why "countable" caps and "uncountable" review need to be used together. Caps hold the floor; review holds the judgment. You can't do without either.
The default behavior of an LLM coding agent is to reuse and extend existing abstractions rather than reassess whether they're appropriate. This stems from the abundance of "follow the existing interface" examples in training data, and from the fact that agents typically lack an understanding of the historical decision context in a codebase. When it sees an existing options object or a general helper, it treats it as an established contract, builds new code around it, and won't proactively ask "is this abstraction itself necessary?"
This trait amplifies the spread of over-engineering. Human teams have a culture of "code archaeology" and periodic refactoring that regularly questions the reasonableness of existing abstractions; agents lack this self-examination. Once a parameter reserved for a hypothetical requirement is merged, it shows up in every subsequent call site, serialization format, test case, and doc, gradually hardening into an interface contract, and the cost of removing it rises exponentially over time.
A common way to counter this is to explicitly add constraints to the agent's context, such as "implement only the requirements covered by the current test cases, don't add unused parameters or config options," and to make "does this serve the current requirement" a separate check question in review, rather than relying on the linter to discover it automatically.
One-line Summary
Mechanical caps only hold the floor of code simplicity; the real discipline is asking in review whether an abstraction serves the requirement in front of you.
Paragraph Summary
Mechanical rules like parameter caps, cyclomatic complexity, and function-length limits automatically catch obviously bloated code, and they're a floor worth keeping—but they can't answer the judgment question of "is this even necessary." The article points out that the most insidious bloat is the version that "looks more general": a function that starts from a single use case and lands just inside the parameter limit, with a tidy diff, no rule firing, yet paying for a hypothetical future. Lowering the cap to three doesn't close the gap; it just makes the fourth parameter reappear as an options object or the like. The real discipline is a human asking in code review: does this version serve the requirement in front of me? AI agents amplify the problem further—they treat existing abstractions as given facts and keep extending them, hardening over-engineering into infrastructure.
Related articles

Free Unlimited Claude Code Solution: Agnes AI + CC Switch Setup Guide
No paid subscription needed — pair the free AI platform Agnes AI with the open-source tool CC Switch to provide a backend for Claude Code, enabling unlimited free calls to text, image, and video models. This guide covers the full setup, test results, and usage boundaries.

A Complete Zero-Cost Guide to Getting Started with Claude Code for Beginners in China
A beginner's guide to Claude Code for users in China: two installation methods (command line and IDE plugin), full configuration steps for replacing the official API with the free domestic LongCat model, and a hands-on demo of building a small tool with natural language, plus tips.

When the NSA Gets Involved: The AI Supply Chain Crisis Behind Open-Source CVEs and API Key Hijacking
Multiple open-source projects disclosed severe CVEs in one day, PHP flaws alarmed the FBI, and Railway and OpenCode were breached with API keys stolen. This article breaks down the vulnerability window, AI-accelerated exploitation, and the AI supply chain crisis behind the API key black market, with a developer defense checklist.