code documentation best practices

9 Code Documentation Best Practices, With Examples

By Zest | Updated | 8 min read
A laptop on a desk with the words Code Docs Guide on its screen

Good documentation is short, lives next to the code, changes in the same pull request, and is checked by CI like everything else. Code shows what a program does and how; docs explain why, which code can’t.

This guide covers nine practices: self-documenting code, inline comments, API docs, READMEs, docstrings, docs as code, architecture decisions, automation, and the instruction files coding agents read.

1. Write self-documenting code

The best documentation starts before the first comment: code whose names and structure make its purpose obvious. Comments and code drift apart; a clear name doesn’t.

Clear code lowers the load on the next reader, who shouldn’t have to cross-reference another document to understand a function.

How to write self-documenting code

  • Use descriptive names: variable and function names should state their purpose. Avoid single letters and vague abbreviations.
    • Bad: const d = new Date();
    • Good: const lastLoginDate = new Date();
  • Keep functions to one job: a function that does one thing gets an accurate name and is easy to follow.
    • Bad: a function named handleUserData() that fetches, validates and saves user data.
    • Good: three functions: fetchUserData(), validateUserProfile() and saveUserToDatabase().
  • Replace magic numbers: give unexplained literals a named constant.
    • Bad: if (user.role === 3) { /* ... */ }
    • Good: const ADMIN_ROLE = 3; if (user.role === ADMIN_ROLE) { /* ... */ }

Self-documenting code handles the line-by-line “what,” so the rest of your documentation can focus on the “why”: decisions, business rules and system design.

2. Use inline comments for the why

An inline comment should explain why the code does something, not restate what it does. Comment the non-obvious: a workaround for a library bug, a business rule, a performance trick that looks wrong.

  • Explain complex logic: describe the strategy when the approach is unusual.
    • Bad: // Loop through the items (obvious from the code).
    • Good: // Reverse loop: we remove items while iterating, which would skip elements in a forward loop.
  • Use standard tags: TODO, FIXME and HACK make comments searchable and signal an action.
    • Bad: // This is broken.
    • Good: // FIXME: This calculation is off by one on leap years. See ticket JIRA-123.
  • State the reason, not the operation:
    • Bad: // Add 10 to the value.
    • Good: // Add a 10% processing fee, required by the new payment gateway's terms.

The best comments answer the question the next developer will ask.

3. Maintain complete API documentation

API documentation is the contract for every service other systems call: each endpoint, its parameters, its responses and its errors. Without it, consumers guess, read your source or open support tickets.

A laptop showing code, with the caption API docs

  • Generate from the source: use an OpenAPI specification, JSDoc or Sphinx so the reference changes with the code.
  • Show complete examples: for each endpoint, give requests in common forms (cURL, Python, JavaScript) and show both success and error responses.
  • Make it interactive: Swagger UI or Postman let readers make live calls from the docs.
  • Document the rest: authentication, rate limits, versioning and deprecation policy.

Stripe’s and Twilio’s API references are good models: treat the docs as part of the product.

4. Write a README that gets people started

The README is the front door: what the project does, how to set it up, and how to contribute. It is often the only document a newcomer reads.

A laptop showing a document titled Project Readme, next to two binders on a desk

  • Open with a one-line description: the project name and what it does, with build, version and license badges at the top.
    • Bad: “This is a project.”
    • Good: “Project-X: a lightweight state management library for React.”
  • Add a quick start: the fastest path to a working example.
    • Bad: a long explanation of configuration before the install command.
    • Good: a “Quick start” section with npm install project-x and a three-to-five-line example.
  • Structure it: headings such as Installation, Usage, Configuration, Contributing and License, with a table of contents if it’s long.

A README is an entry point, not the whole manual. Link out to the API reference, architecture notes and a CONTRIBUTING.md, and update it with every release.

5. Write docstrings for functions and classes

Docstrings are structured comments on modules, classes and functions that tools can extract into reference docs and IDE tooltips. They define what a function does, what it expects and what it returns.

Follow your language’s format. A plain comment like // gets a user by their id, returns the user object is invisible to tools. A JSDoc block is not:

/**
 * Retrieves a user from the database by their unique ID.
 * @param {string} userId - The unique identifier for the user.
 * @returns {User|null} The user object or null if not found.
 */
function getUserById(userId) { /* ... */ }

Document parameters and return values. Instead of # a function that adds two numbers, write a Google-style Python docstring:

def add_numbers(a, b):
    """Adds two numbers together.

    Args:
        a (int): The first number.
        b (int): The second number.

    Returns:
        int: The sum of the two numbers.
    """
    return a + b

Document exceptions and side effects. Instead of // this might fail if the user doesn't exist, write Raises: ValueError: If a user with the specified ID is not found. in the docstring.

Because docstrings sit on the code, they get updated in the same change, and the generated reference stays in sync.

6. Keep docs close to the code (docs as code)

Docs as code means the documentation lives in the same repository as the code, changes in the same pull request, and is built and checked by CI. It gets reviewed, versioned and released with the software it describes, so it stops going stale in a separate wiki.

  • Create a docs/ directory: a common convention at the repository root that contributors can find.
  • Write in plain text: Markdown diffs and reviews like code.
  • Update docs in the same pull request: make it part of your definition of done, with a checklist item in the PR template.
    • Good: a PR that adds an API endpoint also adds docs/api/orders.md explaining it.
    • Bad: merging the feature and opening a ticket to “update the wiki later.”
  • Build and publish from CI: a static site generator such as Docusaurus, MkDocs or Sphinx can build the docs site from the repository on every merge to the main branch.

7. Record architecture and design decisions

Architecture documentation answers the high-level “why”: the system’s structure, the key decisions and the trade-offs behind them. It keeps that knowledge after the people who made the decisions move on.

A whiteboard covered in system diagrams beside a sign reading Design Decisions

  • Write Architecture Decision Records (ADRs): a short file per significant decision, with the context, the decision and its consequences. Keep them in the repository (for example docs/adrs/); when a decision changes, add a new ADR that supersedes the old one.
  • Draw diagrams at the right level: the C4 model (Context, Containers, Components and Code) gives each audience its own level of detail.
  • Explain the choice: say why you picked PostgreSQL over MongoDB, or services over a monolith, and what you gave up.

8. Automate documentation generation and checks

Manual docs drift from the code. Generate what you can from the source and let CI check the rest, so broken docs fail the build like broken code.

  • Generate in CI: OpenAPI tooling for APIs, Javadoc for Java, rustdoc for Rust. Run the generator on every build.
    • Action: add a step to your GitHub Actions or Jenkins pipeline that runs the generator and fails if documentation is missing or malformed.
  • Test your code examples: an example that doesn’t run is worse than none. Rust runs the code examples in doc comments as tests with cargo test (rustdoc’s documentation tests), and Python’s doctest module does the same for docstrings.
  • Check links and prose: run a link checker such as markdown-link-check in a pre-commit hook or CI step, and a prose linter such as Vale for spelling and style rules.

9. Write instructions for coding agents

Coding agents read documentation too, and one file is written just for them: CLAUDE.md for Claude Code, AGENTS.md for Codex, Cursor and other agents. The agent reads it at the start of every session, so it’s the place for what you would otherwise repeat in every prompt.

  • What goes in: the commands to build, test and lint; the conventions the code follows; where the main pieces live; and what the agent must not touch.
  • Keep it short: every line is loaded into every session. Link to longer docs (the ADRs, the API reference) instead of pasting them.
  • Move procedures into skills: a multi-step task like “add an API endpoint” or “release a version” fits better as a Claude Code skill, which loads only when the task comes up.
  • Keep it current: it is docs as code too. Update it in the same pull request that changes a command or a convention.

Everything else in this guide helps agents as much as people: clear names, comments that explain why, and an up-to-date README are what the agent reads when it opens a file.

Where to start

You don’t need all nine at once. Start with the gap that hurts most:

  1. Pick one practice. Slow onboarding? Fix the README. Repeated questions about a function? Enforce docstrings.
  2. Add documentation to your definition of done. A PR template item such as “Does this PR update the relevant docs?” makes docs part of review.
  3. Automate the checks. Lint for missing docstrings, check links in CI, and tell your coding agent in CLAUDE.md or AGENTS.md to update docs in the same change.

Zest records each engineer’s coding-agent sessions (Claude Code, Codex, Cursor, Copilot Chat) and links them to the pull requests they led to.