Skip to content
System AI
Open menu

Working Well With AI Coding Assistants: Habits That Improve Code Quality

Concrete habits for getting better code from AI coding assistants: giving context, working in small steps, reviewing like a skeptic, and keeping tests in charge.

By Editorial TeamPublished 7 min read
cargo ship sea

AI coding assistants can write a working function faster than most people can type its signature. They can also produce plausible code that calls functions that don't exist, ignores your project's conventions, or quietly handles the happy path and nothing else. The difference between those outcomes is mostly in how the developer works with the tool.

This guide collects habits that tend to improve the quality of AI-assisted code. It applies whether you use inline autocomplete, a chat panel in your editor, or an assistant that edits multiple files. None of it depends on a specific product.

Understand what the assistant actually knows

An assistant generates code based on patterns it learned during training plus whatever context it's given at the moment. That second part is where most quality problems start.

The assistant typically doesn't know:

  • Your internal libraries, helpers, and naming conventions, unless it can see them
  • Which version of a framework or package you're using
  • Why a strange-looking piece of code exists
  • Your team's decisions about error handling, logging, or data access
  • What your production environment looks like

When the context is thin, the assistant fills gaps with the most common pattern from its training. That pattern may be outdated, generic, or incompatible with your codebase. Most of the habits below are ways to close that gap or catch what slips through.

Give it the context a new teammate would need

Imagine handing the task to a capable engineer on their first day. What would you tell them? That's roughly what the assistant needs.

Point to relevant files

Open or reference the files that matter: the module you're changing, a similar existing implementation, the relevant types or interfaces, and the test file. Many tools use open files or explicitly referenced files as context. A similar existing implementation is especially valuable, because it shows conventions better than any description.

State constraints explicitly

Mention things that aren't obvious from the code:

  • "We use the project's own HTTP client wrapper, not raw fetch."
  • "This runs in a worker with no DOM access."
  • "Errors should be returned as result objects, not thrown."
  • "Keep this compatible with the existing public API."

Keep a project instructions file

Many assistants support a persistent instruction file in the repository that is loaded automatically. Use it for conventions you'd otherwise repeat constantly: build and test commands, directory layout, style rules, patterns to prefer and avoid. Keep it short and current. An outdated instructions file is worse than none, because it confidently steers the tool wrong.

Work in small, verifiable steps

Large requests produce large diffs, and large diffs are hard to review. When an assistant writes several hundred lines across many files, errors hide easily and you lose the ability to tell which change caused which problem.

A better rhythm:

  1. Ask for a plan first on anything non-trivial. Review the approach before any code is written. It's far cheaper to redirect a plan than to unwind an implementation.
  2. Implement one piece at a time. A data model change, then the function that uses it, then the endpoint, then the UI.
  3. Run the tests or the code after each step. Confirm it works before moving on.
  4. Commit working states. Small commits give you safe rollback points if the next step goes sideways.

This feels slower. In practice it's usually faster, because you spend less time untangling a big broken change.

Review generated code like a skeptical colleague

Generated code deserves at least the scrutiny you'd give a pull request from someone you've never worked with. Its fluency can lower your guard. Well-formatted, confidently commented code is not the same as correct code.

A review checklist

  • Does every referenced function, method, and package actually exist in the version you use? Invented APIs are a common failure.
  • Are edge cases handled? Empty inputs, null values, large inputs, concurrent calls, network failures, and timeouts.
  • Is error handling real? Watch for errors that are caught and silently ignored, or logged and then treated as success.
  • Does it match project conventions? Naming, file structure, logging, and existing helpers.
  • Is there duplicated logic? Assistants often reimplement something your codebase already has.
  • Are there security issues? Unvalidated input, string-built queries, secrets in code, overly broad permissions, or unsafe deserialization.
  • Did it change anything you didn't ask for? Unrelated edits, reformatting, or removed code.
  • Do you understand every line? If not, ask for an explanation or rewrite it until you do.

That last item matters most. You are responsible for code you merge, regardless of who or what wrote it. Code you don't understand is code you can't debug at 2 a.m.

Keep tests in charge

Tests are the most reliable check on generated code, because they don't care how convincing the code looks.

Write or review tests first

For well-defined behavior, write the tests yourself, or have the assistant draft them and review them carefully, before generating the implementation. Then the implementation has a concrete target. This works especially well for functions with clear inputs and outputs.

Be suspicious of tests written alongside the code

When an assistant writes both code and tests in one pass, the tests often mirror the implementation, including its mistakes. They pass, but they prove little. Check that tests assert meaningful behavior, cover failure paths, and would fail if the logic were wrong. A quick sanity check: break the implementation on purpose and confirm a test catches it.

Watch for tests being weakened

When a test fails, some assistants will "fix" the problem by changing the assertion, skipping the test, or mocking away the thing being tested. Review test changes with extra care, and be explicit in your instructions that failing tests indicate a bug in the code unless you've confirmed otherwise.

Choose the right mode for the task

Different kinds of assistance fit different work.

Task Mode that tends to work Watch out for
Boilerplate, repetitive patterns Inline autocomplete Subtle copy-paste errors
Unfamiliar library or syntax Chat with explanation Outdated or invented APIs
Multi-file feature Plan first, then stepwise edits Large unreviewable diffs
Refactoring Stepwise edits with tests running Behavior changes hidden in cleanup
Debugging Chat with error output and relevant code Guesses presented as diagnoses
Writing tests Draft then careful review Tests that mirror bugs

For debugging in particular, give the assistant the full error message, the stack trace, the relevant code, and what you've already tried. Ask it to explain its hypothesis before proposing a fix. A fix without a stated cause is often a patch over a symptom.

Protect the codebase's long-term health

Assistants optimize for the task in front of them. They don't naturally care about the codebase six months from now. That's your job.

Resist unnecessary code

It costs almost nothing to generate code, but everything generated must be read, maintained, and tested. Push back on over-engineered abstractions, speculative configuration options, and helper functions that duplicate existing ones. Ask "can this be simpler?" as a routine follow-up.

Keep dependencies deliberate

Assistants sometimes suggest adding a package for something that takes a few lines, or suggest packages that don't exist or are poorly maintained. Verify any new dependency exists, is actively maintained, and is actually needed before adding it.

Maintain consistency

When the assistant introduces a second way of doing something your codebase already does one way, such as a different date library or a different state pattern, stop and redirect. Inconsistency compounds quickly when code is produced at speed.

Handle secrets and sensitive code carefully

Understand what your assistant sends to external services and what your organization permits. Practical rules:

  • Don't paste credentials, API keys, or customer data into prompts.
  • Keep secrets out of files the assistant reads automatically, such as by relying on environment variables and ignored files.
  • Follow your organization's policy on which repositories may be used with which tools.
  • Review generated configuration files for hardcoded secrets or permissive defaults.

Keep your own skills sharp

Heavy reliance on an assistant can erode the skills you need to judge its output. A few habits help:

  • Read the code before accepting it, even when you're in a hurry.
  • Occasionally solve problems without the assistant, especially in areas you're still learning.
  • Ask why, not just what. When the assistant uses an approach you don't recognize, have it explain the trade-offs.
  • Learn the underlying system. An assistant can write a database query, but you need to understand indexes to know whether it will be fast.

Engineers new to a language or framework should be particularly careful. The assistant can make you productive before you're competent, which is fine only if you use the time to catch up.

A sample workflow for a small feature

Here is one way the habits fit together for a hypothetical task: adding a rate limit to an existing API endpoint.

  1. Open the endpoint file, an existing middleware example, and the related tests.
  2. Ask for a short plan that uses the project's existing middleware pattern and storage layer.
  3. Review the plan, correct any wrong assumptions, and agree on the approach.
  4. Write or review tests describing the expected behavior: allowed requests, blocked requests, reset timing, and behavior when the storage layer is unavailable.
  5. Ask for the implementation, one file at a time.
  6. Run the tests. When one fails, read the failure yourself before asking for a fix.
  7. Review the diff against the checklist, paying attention to error handling and unrelated changes.
  8. Commit, then open a pull request that a human reviews as usual.

Nothing in this workflow is exotic. It's ordinary good engineering practice, applied more deliberately because code now arrives faster than before.

Key takeaways

  • Give the assistant the context a new teammate would need: relevant files, constraints, and a short, current project instructions file.
  • Work in small, tested steps and ask for a plan before implementing anything non-trivial.
  • Review generated code for invented APIs, missing edge cases, silent error handling, and security issues, and never merge what you don't understand.
  • Let tests drive quality, and watch closely for tests that mirror bugs or get weakened to pass.
  • Guard long-term codebase health by rejecting unnecessary code, unneeded dependencies, and inconsistent patterns.
  • Keep secrets out of prompts and keep your own skills strong enough to judge the output.