Good self-documentation in AI-assisted coding means that someone can understand, use and safely change the result after the conversation has disappeared. Start with clear code. Add precise interface contracts and the reasons that the implementation cannot explain on its own. Keep setup commands, operational instructions and consequential design decisions in maintained repository files.
AI can help draft all of these, but the explanation it produces needs review alongside the code. An assistant can describe a mistake convincingly or invent a reason for an unusual branch. Documentation becomes useful when it preserves verified knowledge and exposes unresolved assumptions.
This guide proposes a practical workflow for developers and engineering leads. Its access-window example is fictional. We have not run a study of developer comprehension or independently compared coding assistants for this article.
Make the behavior visible before explaining it
Ask the coding assistant to use names from the business domain, make units explicit and keep related logic together. A reader should be able to distinguish a duration from a timestamp, a requested change from a confirmed one, and a predicate from an action with side effects.
Avoid adding layers solely to make the code look sophisticated. A small, direct function can explain a rule better than a generic framework with several configuration switches. Follow the repository’s existing conventions so readers can transfer what they already know.
Google’s engineering review guidance treats understandable complexity, naming, comments and documentation as related review concerns. It recommends simplifying unclear code and preserving rationale in useful comments. It also distinguishes implementation comments from documentation explaining how an interface should be used. This is practitioner guidance, not experimental proof that a naming style reduces AI coding errors.
Use that distinction when prompting: request a clear implementation first, then ask which important information remains unavailable to a reader. Repeating each statement in English adds text without necessarily adding understanding.
Document contracts that names and types leave open
Consider this deliberately vague Python function:
def check(t, n):
return n >= t
The comparison is easy to read. Its meaning is not: which argument is the observation time, what happens at equality, and what time conventions apply?
In a fictional application, access windows include their start instant and exclude their end instant. The following illustrative helper expresses only whether the window has ended:
from datetime import datetime
def has_access_window_ended(
*,
ends_at_utc: datetime,
now_utc: datetime,
) -> bool:
"""True at or after the end instant.
Inputs must be timezone-aware UTC.
"""
return now_utc >= ends_at_utc
The function assumes those input preconditions have been checked at the application’s boundary; its names and type annotations do not enforce UTC. It also does not decide whether access has started or whether a user is authorized. That limited responsibility is part of making it understandable.
John Ousterhout’s 2018 Stanford notes on comments argue that code alone cannot cleanly communicate all design knowledge. His examples include units, boundary conditions, null meaning and the separation of interface from implementation documentation. These are useful questions for AI-generated code even when its syntax appears obvious.
For the fictional rule, retain an executable example at the exact end instant as well as examples just before and after it. Such examples make the agreed boundary visible and can detect an accidental change from >= to >. They do not establish that the whole access system is correct.
Preserve the reason without inventing a history
An interface contract describes what callers can rely on. Rationale explains why a choice was made. In the example, an approved design note could explain that adjacent access windows must not both include the same boundary instant. Keep that reason near the rule or in a linked decision record if it spans several components.
Ask AI to locate the actual evidence for a rationale: a requirement, issue, design decision or established external contract. If it cannot find one, let it identify an assumption or ask for a decision. A comment such as “required for compatibility with older customers” is misleading when nobody has established that history.
Separate observed behavior from intended behavior. When existing code and the stated requirement disagree, document the discrepancy for resolution. Generating a docstring from the implementation can accurately describe what it currently does while preserving the wrong business rule.
Also review stale comments in the changed area. A new branch can make an older warning false, and a removed workaround can leave an obsolete explanation behind. Update the implementation and its explanation together so the next reader does not have to decide which one to trust.
Treat generated comments as claims to check
The December 2025 revision of Guelman and colleagues’ study evaluates generated Javadoc against curated comments and human assessments. It finds limits in how well text-similarity metrics capture those assessments. The study uses Java code elements and historical models, with target code supplied without surrounding project context; it does not establish the reliability of today’s repository-aware coding agents.
For a code review, the practical implication is to inspect the claims. Does a function documented as read-only write to a cache? Does a “returns an empty list” claim match a branch that raises an exception? Does the promised unit match what callers supply? A polished paragraph or a high similarity score cannot answer those questions on its own.
Check public contracts against relevant call sites and tests. Check rationale against its source. Keep uncertainty explicit when context is missing. Avoid accepting extra claims merely because the assistant added them while making the documentation sound complete.
Put each explanation where its reader needs it
The Diátaxis documentation framework separates learning tutorials, task-oriented how-to guides, technical reference and explanation. Its useful contribution here is the reader’s purpose: someone looking up a parameter needs a different document from someone trying to understand an architectural tradeoff.
The following placement guide is an editorial proposal for an ordinary repository. Adapt it to existing conventions rather than creating empty folders for every category.
| Information to preserve | Suitable home | What to avoid duplicating |
|---|---|---|
| Local behavior and domain meaning | Names, types and focused functions | Line-by-line prose translating obvious statements |
| Inputs, outputs, errors and side effects | Interface docstring or API reference | Internal implementation detail callers do not need |
| Non-obvious decision and rejected alternative | Nearby rationale or a linked decision record | An invented explanation of past intent |
| Setup, verification and operational recovery | README or task-specific runbook | Commands copied into several competing guides |
| Instructions for future coding agents | Repository agent instructions linking canonical docs | A second, drifting copy of project architecture |
Keep a clear authoritative location for facts that change. For example, a short agent instruction can point to the maintained build guide instead of repeating its complete command sequence. That link does not guarantee that every assistant will read it; verify behavior in the actual workflow when it matters.
Include documentation in the definition of a finished change
Give the assistant a bounded documentation task with the implementation request. A reusable prompt could be:
Read the relevant code, tests and project documentation first.
Implement the requested change using existing naming conventions.
Document non-obvious contracts and verified rationale near their users.
Update affected setup, API or operational instructions in their
canonical locations. Flag missing requirements; do not invent intent.
Report the checks actually run and any examples left unverified.
Review the diff for documentation impact even when no Markdown file changed. A renamed environment variable, changed default or new error condition may require an update to an existing guide. Conversely, a local refactor may need no new guide if its public behavior remains intact.
Verify commands and examples in the appropriate environment. A syntactically valid command can still use an obsolete flag or omit a prerequisite. Generated API reference can keep signatures current while leaving behavioral descriptions stale. Record what was exercised and what remains a proposed example.
Review from the next reader’s position
Before merging, set aside the chat and use the repository to answer three questions: what does this component promise, why does its non-obvious behavior exist, and how do I verify a safe change? Missing answers identify documentation work more reliably than a target number of comments.
Use tests to protect important behavior, while preserving the reasoning that tests cannot express clearly. Our guide to reviewing AI-generated regression tests explains how to check whether a proposed test detects the intended mistake. Documentation and tests should support the same contract without mechanically repeating each other.
Start with the code being changed. Improve its names, clarify one consequential boundary, retain a verified reason and update the affected instructions. The result should leave the next developer with enough durable context to continue the work, without needing to reconstruct an AI conversation.