Skip to main content

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, Handler as primary names — these are SRP violations waiting to happen
  • Name should match the single responsibility: InvoicePersistence not InvoiceStuff

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
Fix pattern — extract nested conditionals:

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
Anti-pattern: 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:
  1. Early return / guard clauses — eliminate the else branch by returning early when a condition fails
  2. Extract condition into a named boolean function: if is_eligible_for_discount(user) reads better than if user.age > 65 and user.account_type == "premium" and not user.has_discount
  3. 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)
Do not comment when:
  • The function name already says it: # This function groups users by id before group_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.
Applies to:
  • Numeric literals (0.1, 86400, 404, 3)
  • String sentinels ("admin", "pending", "USD")
  • Array indices used as semantic positions (data[2] meaning “the third column”)
Where to define constants:
  • 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.

  • 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