2026-03-23 - Juan

Writing Code with AI: Best Practices

AI is very good at accelerating software work. It is not very good at owning the consequences.

That distinction matters. If you treat AI like a fast collaborator, it can remove a lot of mechanical effort. If you treat it like an infallible engineer, it will quietly create debt, regressions, and code nobody wants to maintain.

The best results usually come from using AI as a leverage tool inside a disciplined engineering process, not as a replacement for one.

Start with a precise task

Most bad AI-generated code starts with a vague prompt. "Build authentication" or "optimize this component" is not enough context for a good result.

A better prompt defines:

  • the goal
  • the constraints
  • the relevant files
  • what success looks like

For example:

Update the billing form in `src/app/settings/billing.tsx`.
Keep the existing API contract.
Do not change styling.
Add client-side validation for empty cardholder name and invalid postal code.
Return a minimal patch and explain tradeoffs.

That gives the model something concrete to solve. More importantly, it narrows the blast radius.

Keep tasks small enough to review

AI performs best when the unit of work is small, local, and easy to verify. Ask it to make one meaningful change, not six loosely related ones.

Good AI tasks:

  • refactor one component
  • add tests for one module
  • explain one stack trace
  • migrate one API handler
  • write one database query

Risky AI tasks:

  • redesign the whole architecture
  • rewrite a large subsystem in one shot
  • make broad changes without a clear spec
  • "clean up" a codebase with no boundaries

If a diff is too large to review carefully, it is too large to delegate to AI.

Give it the real context

AI fills gaps with plausible guesses. That is useful until those guesses hit production.

Before asking for code, provide the surrounding context that actually constrains the solution:

  • existing interfaces
  • runtime expectations
  • framework version
  • patterns already used in the repo
  • performance or accessibility requirements
  • things that must not change

This is especially important in mature codebases. The technically correct answer is often still the wrong answer if it ignores local conventions.

Ask for diffs, not essays

Long explanations feel helpful, but they often hide weak implementation details. When I use AI for code, I usually want one of these:

  • a minimal patch
  • a concrete implementation
  • a short list of assumptions
  • a focused explanation of a specific decision

The more room you give the model to perform, the more likely it is to generate polished but low-value output.

Make verification part of the prompt

Do not ask AI to write code without also asking how the change should be checked.

At minimum, require one or more of:

  • unit tests
  • integration tests
  • manual verification steps
  • lint and typecheck expectations
  • edge cases that should still pass

A useful pattern is: implement the change, then explain how to verify it, then identify likely failure modes.

That last part matters because AI is often better at generating code than at noticing where the code is fragile.

Prefer boring code over clever code

AI has a bias toward code that looks impressive. That usually means too much abstraction, helper functions that only exist once, and patterns that are harder to debug than the original version.

Push it toward the simplest thing that fits the codebase:

  • fewer layers
  • explicit names
  • standard library over custom utilities
  • straightforward control flow
  • small functions with obvious responsibilities

If the generated version is harder to explain than the hand-written version, it is probably worse.

Treat every output as untrusted until reviewed

This is the main rule.

AI can produce code that compiles, passes a happy-path test, and is still wrong in subtle ways. Common failures include:

  • misunderstanding the data model
  • changing behavior outside the requested scope
  • inventing APIs that do not exist
  • swallowing errors
  • breaking edge cases
  • introducing security or performance issues

Reviewing AI code is not optional. It is the cost of using it responsibly.

Use AI more for iteration than authorship

The highest-value use cases are usually not "write the whole feature from scratch."

They are:

  • generating a first draft
  • proposing refactor options
  • translating intent into boilerplate
  • writing tests around existing behavior
  • summarizing unfamiliar code
  • turning review comments into concrete edits

In other words, AI is strongest when it compresses iteration time. You still need engineering judgment to decide what should ship.

Keep your own reasoning visible

One subtle risk with AI-assisted coding is that teams stop writing down why a change exists. The model generates the patch, everyone reviews the code, and the reasoning disappears.

Do not let the prompt become the only design document.

Keep important decisions in places humans will actually revisit:

  • commit messages
  • pull request descriptions
  • code comments where necessary
  • ADRs for larger choices

Future maintainers need the rationale, not just the diff.

Know when not to use it

There are cases where AI is the wrong tool:

  • high-risk migrations with unclear requirements
  • security-sensitive logic
  • incidents where correctness matters more than speed
  • code you do not yet understand well enough to review

If you cannot evaluate the output, you should not be delegating the task.

A simple workflow that works

The process I keep coming back to is:

  1. Define a narrow task.
  2. Provide the exact files and constraints.
  3. Ask for a minimal change.
  4. Review the diff carefully.
  5. Run tests and manual checks.
  6. Refine in small iterations.

That workflow is not glamorous, but it is reliable. And reliability is what makes AI genuinely useful in a professional codebase.

Final thought

AI does not remove the need for taste, judgment, or accountability. It raises the premium on them.

The engineers who get the most from AI are not the ones who accept the fastest answer. They are the ones who know how to frame a problem well, constrain the solution, and reject work that does not meet the bar.

Used that way, AI is not magic. It is just very effective leverage.