DRY
DRY violation detection without false confidence
Repeated lines are a symptom. Duplicated knowledge is the problem. A useful DRY check finds candidates for human review without pretending that every repeated block should become a shared function.
What DRY actually asks
Don’t Repeat Yourself is often reduced to “extract repeated code.” That is too narrow. The principle is about knowledge: every piece of authoritative business logic should have one home. Two functions can share text while representing different rules. Two functions can look different while encoding the same rule twice.
A tax calculation copied into an invoice module is duplicated knowledge. Two validation functions that happen to use the same loop shape may be independent. The first is a maintenance risk. The second is a coincidence.
Ask before extracting: if the rule changes, should both sites change together? If the answer is not an immediate yes, the duplication may be intentional.
Why automated detection is hard
A scanner can normalize whitespace, identifiers, and literals to find structurally similar blocks. It can also compare function bodies. Both are useful heuristics, and both produce false positives:
- Different policies. Two pricing rules use the same arithmetic today but are owned by different teams and must evolve independently.
- Framework boilerplate. Test setup, serializers, and adapters often share a shape for reasons outside the domain.
- Generated code. A repeated block may be an output artifact, not hand-maintained knowledge.
- Coincidental similarity. Two loops with the same accumulator are not necessarily the same concept.
Text similarity is evidence. It is not a conclusion.
How pragmatic-audit detects duplication
The PP-DRY rule uses two conservative signals:
- Normalized blocks. It looks for windows of at least six non-trivial lines that repeat after normalizing whitespace, identifiers, and literals.
- Duplicate functions. It compares function bodies with the same normalization and reports a pair when the bodies are effectively identical.
The output names both locations and the smallest useful fix. It does not rewrite the code, and it does not assume the correct extraction is obvious.
$ pragmatic-audit .
PP-DRY src/orders.py:38
Function `calculate_invoice` duplicates `calculate_total` at src/orders.py:30.
Fix: Keep one authoritative representation and reuse it.
A review procedure for a DRY finding
1. Name the knowledge
Write down the rule in one sentence. “Tax is 20% of the line-item total.” If you cannot name it, the duplication may be structural rather than semantic.
2. Decide who owns the rule
Find the module that should be authoritative. If two teams own the same rule independently, the real problem is organizational, not a missing helper.
3. Check the change coupling
Imagine the rule changes. Would both call sites need to change? If yes, extract. If only one should change, keep them separate and document the distinction.
4. Prefer the smallest extraction
A named function is often enough. A new abstraction layer, base class, or configuration system is usually too much. The goal is one authoritative home, not the most flexible possible design.
5. Keep the test honest
After extraction, the existing tests should still describe behavior. If the refactor changes a public API, add a test for the compatibility you intend to keep.
When to suppress the finding
A suppression is legitimate when the duplication is intentional and the reason is stable. Write the reason next to the code or in the file header:
# pragmatic-audit: ignore PP-DRY pricing and billing rules evolve independently
def calculate_billing_total(items):
...
Do not suppress a finding merely to make the report quieter. A suppression without a reason is a broken window.
What a clean DRY result does not mean
No duplication finding means no repeated block large enough to cross the scanner’s threshold. It does not mean the code has one authoritative model of the domain. Knowledge can be duplicated across names, schemas, documentation, and teams without looking alike.
Use the scanner to find candidates. Use the domain to decide.