Code Quality Heuristics
Tactical rules for writing readable, maintainable code: naming, function discipline, cognitive complexity, comment discipline, magic number elimination, and code smell identification. Agent guidance: apply these when generating or reviewing code. When a heuristic is violated, flag it in review and fix it before marking work complete. These rules apply to AI-generated code with equal force — LLMs are known to violate DRY, produce long functions, and generate inconsistent naming.Agent Trigger
Apply when: Generating or reviewing any code — naming, function size, complexity, comments, magic numbers. Rule of thumb: Small single-responsibility functions, intention-revealing names, no magic numbers; flag and fix on violation before marking work complete.Naming Conventions
Core rule: A name should tell you why it exists, what it does, and how it is used. If a name requires a comment to explain it, the name is wrong.Variables
Functions
- Name with verb + noun:
calculate_total,fetch_user,validate_email - Boolean-returning functions:
is_,has_,can_,should_prefixes - Avoid
process_data,handle_stuff,do_work— too generic to be searchable or testable
Classes
- Noun or noun phrase:
InvoicePrinter,UserRepository,PaymentGateway - Avoid
Manager,Helper,Util,Handleras primary names — these are SRP violations waiting to happen - Name should match the single responsibility:
InvoicePersistencenotInvoiceStuff
Language-specific conventions (follow the project’s existing style)
Function Discipline
Size
There is no universal line count limit, but the heuristic is: a function should fit on one screen without scrolling. When it does not, look for extraction opportunities. Trigger for extraction: sections of a function that can be named with a verb phrase. If you’re writing a comment like# validate inputs before a block, that block is a candidate to become validate_inputs().
Single Responsibility
A function should do one thing. Signal that it does multiple things:- The name contains “and”:
validate_and_save,fetch_and_format - The function has multiple levels of abstraction mixed together (high-level orchestration + low-level string manipulation in the same body)
- Testing requires setting up multiple unrelated concerns
Parameter Count
- 0–2 parameters: ideal
- 3 parameters: acceptable, review whether they cluster into a concept
- 4+ parameters: strong signal to introduce a parameter object or restructure
create_user(name, email, age, role, department, is_active) — introduce UserSpec or CreateUserRequest.
Levels of Abstraction
A function should operate at one level of abstraction. Mixing high-level orchestration with low-level mechanics forces readers to context-switch constantly.Cognitive Complexity
Cognitive complexity measures how hard code is to understand — not just how many branches it has (cyclomatic complexity), but how nested and non-linear the control flow is. Key contributors to cognitive complexity:
Mitigations:
-
Early return / guard clauses — eliminate the
elsebranch by returning early when a condition fails -
Extract condition into a named boolean function:
if is_eligible_for_discount(user)reads better thanif user.age > 65 and user.account_type == "premium" and not user.has_discount - Flatten loops — if you have a loop inside a loop inside an if, consider whether the inner loop belongs in its own function
Comment Discipline
When to comment
Comments should explain why, not what. If the code clearly states what it does, a comment restating it is noise. Comment when:- The code does something non-obvious for a non-obvious reason (performance hack, workaround for external system behavior, regulatory requirement)
- There is a known edge case or gotcha a future reader should know before modifying
- The function has a non-trivial contract (preconditions, postconditions, exceptions)
- The function name already says it:
# This function groups users by idbeforegroup_users_by_id()is redundant - You’re explaining what a standard algorithm does — if the reader doesn’t know merge sort, that’s a prerequisite problem, not a documentation problem
- You’re tracking changes (
# Added by Alice, 2024-03-01) — that’s version control’s job
Docstrings / doc comments
Use for public APIs. Include: what the function does, parameters, return type, exceptions raised, non-obvious behavior warnings.Stale comment anti-pattern
A comment that describes what the code used to do (before a refactor) is worse than no comment — it actively misleads. When modifying code, always update or delete adjacent comments.Magic Numbers and Constants
Definition: A magic number is a numeric (or string) literal in code whose meaning is not self-evident. Rule: Replace any literal whose purpose is not immediately obvious at the call site with a named constant.- Numeric literals (
0.1,86400,404,3) - String sentinels (
"admin","pending","USD") - Array indices used as semantic positions (
data[2]meaning “the third column”)
- Module/file level for shared constants
- Class-level for class-specific constants
- Avoid defining them as function-local unless they are truly local to one function
Code Smell Taxonomy
Code smells are patterns that indicate deeper problems. They are not bugs — the code may work — but they signal that a refactor is warranted.Structural smells
Complexity smells
AI-specific smells (common in LLM-generated code)
- Overly long methods: LLMs optimize for function, not conciseness. Review generated functions > 20 lines.
- Duplicated logic: LLMs don’t know the full codebase. Generated code often reimplements existing utilities.
- Inconsistent naming: LLM-generated code in an existing file may use different naming conventions than the file.
- Redundant conditions:
if x == True:,if len(list) > 0:— LLMs produce defensive conditions that add noise. - Hardcoded secrets: LLMs may include example API keys or credentials. Always scan generated code before committing.
- Insecure dependencies: LLMs may suggest packages with known CVEs. Verify all suggested libraries.
Related pages
- Software Design Principles — SOLID, DRY, YAGNI, KISS — the design principles behind these heuristics
- Refactoring Techniques — Mechanics for moving from smell to clean code
- Unit Testing — Function discipline and SRP make code testable; testability is a quality signal
- Deep Modules — Ousterhout’s framing: complexity hides behind narrow interfaces; function discipline is complementary