On this page
An AI coding assistant can produce a plausible implementation faster than a reviewer can establish whether it is correct. The solution is not to read faster. It is to make the change smaller, define evidence before generation, and separate the assistant's explanation from independently verified behavior. The developer accepting the change still owns its consequences.
Establish a contract before generating code
Write a short task contract containing the desired behavior, files allowed to change, non-goals, and observable acceptance criteria. For example: add a parser for a partner response, preserve existing error handling, introduce no dependencies, and reject malformed identifiers. This gives both the assistant and reviewer a boundary more useful than “make this better.”
For a JSON integration, use JSON to TypeScript with a sanitized sample to sketch the expected shape. Compare the generated interfaces with the actual provider contract, then give the assistant the corrected version. A sample is not a schema: optional properties, null values, and alternative response variants may be absent from one example.
Decide what information the assistant may receive. Remove credentials, customer records, private keys, and production logs unless your organization's approved environment explicitly permits them. Check retention and training policies rather than assuming all assistants handle data identically. Restrict tool permissions to the task's needs and keep destructive commands behind explicit approval.
Ask for a plan and a reviewable diff
Request a brief plan before edits: which functions change, what assumptions are being made, and which tests will demonstrate the result. Reject plans that introduce a broad abstraction for a narrow bug. Scope control is particularly valuable when generated code expands into unrelated cleanup that makes the behavioral change difficult to isolate.
A reusable prompt can be plain text rather than a complicated automation system:
Implement only the response parser and its tests.
Do not change dependencies, public exports, or unrelated formatting.
Treat network data as unknown until validated.
First list assumptions and the intended failure behavior.
After editing, list changed files and commands actually run.
Distinguish verified results from checks you could not execute.Keep the first patch small enough to understand in one sitting. Inspect the full diff, including lockfiles, configuration, deleted tests, and generated artifacts. Do not review only the files highlighted in the assistant's summary. A correct function can arrive alongside an unsafe script change or a relaxed lint rule.
Consult your platform's guidance, such as GitHub's responsible-use documentation, for product-specific limitations. Treat that guidance as a starting point, not evidence that a particular patch is correct.
Verify interfaces and trust boundaries manually
Read the modified function and its callers without relying on the generated explanation. Identify what enters the system, where it becomes trusted, and which side effects it can trigger. Type assertions are not validation. A cast can silence the compiler while leaving malformed external input untouched.
This example intentionally accepts unknown input and narrows only the property required by the feature:
type PartnerRecord = { id: string };
export function parsePartnerRecord(value: unknown): PartnerRecord {
if (typeof value !== "object" || value === null) {
throw new Error("Expected an object");
}
if (!("id" in value) || typeof value.id !== "string") {
throw new Error("Expected a string identifier");
}
if (value.id.trim().length === 0) {
throw new Error("Expected a non-empty identifier");
}
return { id: value.id };
}The example does not establish uniqueness, authorization, or an identifier format beyond non-empty text. Those are separate requirements. Compare the implementation against the TypeScript narrowing documentation rather than accepting a confidently stated language rule.
Check every new API and package against official documentation and the installed version. Assistants can combine methods from different releases or suggest nonexistent options. For dependencies, verify the exact package name, maintainer, license, and need. Do not install a package simply to see whether the generated import starts working.
Test behavior independently of the implementation
Write acceptance tests from the task contract, not from the generated function's structure. If the assistant writes both implementation and tests, the same mistaken assumption can appear in both. Independent test design means adding cases that could falsify the proposed solution, not merely confirm its happy path.
For the parser, test null, arrays without identifiers, missing properties, numeric identifiers, whitespace-only text, and a valid record containing unrelated fields. Ask whether rejecting or preserving extra fields matters. Add a regression test that fails against the original defect and passes after the fix; otherwise the test may not actually protect the intended behavior.
Run relevant unit tests, type checks, and lint commands yourself or inspect trustworthy execution logs. A statement that tests “should pass” is not a test result. Record commands, exit status, and any environmental limitations. For UI changes, inspect keyboard interaction and visible error states as well as snapshots. The accessibility guide provides useful review questions for that manual pass.
Use MDN's JavaScript reference when checking runtime behavior. Compilation success does not prove that date parsing, coercion, asynchronous ordering, or browser support matches the product's requirements.
Review maintainability and capture the decision
Once correctness looks sound, ask whether the code fits existing conventions. Prefer established helpers over near-duplicates, ordinary control flow over clever abstractions, and comments explaining constraints over comments restating syntax. Readable ownership matters because the next maintainer may never see the original conversation.
Remove unused helpers, speculative options, and tests that assert implementation trivia. Check that error messages help callers act without exposing sensitive values. If generated code contains copied-looking license headers or unfamiliar large blocks, investigate provenance under your organization's policy rather than assuming generation removes licensing obligations.
Prepare a pull-request summary describing the behavioral change, evidence, remaining uncertainty, and any approved use disclosure. The author should be able to explain every changed line. If they cannot, request a smaller implementation or stop the merge. Use the developer tools category to build consistent team habits, but keep the binding policy in version-controlled project documentation.
Before approval, perform one adversarial pass: imagine the change is wrong despite all green checks. Ask what production condition the tests omitted, which permission boundary could be crossed, and whether a rollback restores the previous behavior. For asynchronous code, consider duplicate requests, cancellation, and stale responses. For filesystem tools, consider paths outside the intended workspace. Write down the most credible remaining risk and decide whether it needs another test or explicit acceptance. This exercise is not an invitation to demand impossible certainty; it turns vague unease into a concrete question that a reviewer can resolve.
Key takeaways
- Define acceptance criteria and permissions before asking for implementation.
- Review the entire diff, including configuration and dependencies.
- Verify runtime boundaries instead of trusting generated types or explanations.
- Design tests to challenge assumptions and record actual execution results.
- Merge only code a human owner can explain and maintain.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor