Steven's Knowledge

Best Practices

How to use AI tools effectively without losing engineering skill, covering prompt engineering, review discipline, security, and team practices

Best Practices for AI-Assisted Development

AI tools make you faster. They do not make you better. The difference between someone who uses AI well and someone who uses it poorly is discipline — the discipline to review, verify, and understand every line of code before committing it.

This page covers the practices that separate effective AI-assisted development from reckless copy-paste.

Prompt Engineering for Code

The quality of AI output is directly proportional to the quality of your input. Vague prompts produce vague code.

Be Specific About Context

Bad prompt:

Write a function to process user data.

Good prompt:

Write a TypeScript function that validates and normalizes user registration data.
- Input: raw form data from a Next.js API route (req.body)
- Use Zod for validation
- Normalize email to lowercase, trim whitespace from name
- Return { success: true, data: ValidatedUser } | { success: false, errors: ValidationError[] }
- Follow the discriminated union pattern used elsewhere in this project

The good prompt specifies: language, framework, validation library, normalization rules, return type pattern, and project conventions.

Provide Existing Code as Context

AI generates better code when it can see your existing patterns.

Here are the existing types used in this project:

[paste relevant type definitions]

Here is an example of how other similar functions are structured:

[paste an existing function that follows the pattern]

Now write a similar function that handles [new requirement].

This technique — showing AI what your code looks like before asking it to write more — dramatically improves consistency with your codebase.

Ask for Explanations Alongside Code

Implement a debounce function with TypeScript generics and explain:
1. Why you chose this approach over alternatives
2. How the generic types ensure type safety
3. What edge cases the implementation handles

When AI explains its choices, you can evaluate whether those choices are appropriate for your context. Code without rationale is harder to review.

Iterate Rather Than Start Over

Start broad, then refine through follow-up prompts:

[First prompt] Implement a connection pool for PostgreSQL.

[Follow-up] Now make it handle connection timeouts — if a connection
is idle for more than 30 seconds, close it and create a new one.

[Follow-up] Add health check pinging — every 10 seconds, verify that
idle connections are still alive.

[Follow-up] Make it thread-safe for concurrent access from async handlers.

Iterative refinement produces better results than trying to specify everything in one massive prompt.

Reviewing AI Output

This is the most critical skill. AI generates plausible-looking code that may be subtly wrong. Your job is to catch those subtleties.

Read Every Line Like a Junior Wrote It

AI-generated code should receive the same scrutiny as a pull request from a junior developer who is confident but inexperienced. That means:

  • Do not skim. Read every line. AI does not leave TODO comments where it is unsure — it just writes something plausible.
  • Check the logic, not just the syntax. AI rarely produces syntax errors. The bugs are in the logic: off-by-one errors, incorrect boundary conditions, wrong comparison operators.
  • Verify external API calls. AI confidently uses function signatures that do not exist. If the code calls a library method, verify that method exists in the version you are using.

Check the Common Failure Modes

When reviewing AI-generated code, check for these specific issues:

// 1. HALLUCINATED APIs — does this method actually exist?
const result = await prisma.user.softDelete({ where: { id } });
// Prisma does not have a softDelete method

// 2. WRONG ERROR HANDLING — does this catch the right error type?
try {
  await fetch(url);
} catch (e) {
  if (e instanceof TypeError) { /* AI assumes fetch throws TypeError */ }
}
// fetch rejects with a TypeError for network errors, but not for HTTP errors

// 3. SECURITY GAPS — is this actually safe?
const sanitized = input.replace(/<script>/g, '');
// Only removes literal <script>, not <SCRIPT>, <script , etc.

// 4. DEPRECATED METHODS — is this still current?
const hash = crypto.createHash('md5').update(password).digest('hex');
// MD5 for passwords is insecure; should use bcrypt or argon2

// 5. RACE CONDITIONS — is this safe under concurrent access?
const count = await redis.get('counter');
await redis.set('counter', parseInt(count) + 1);
// Not atomic — two concurrent requests can overwrite each other

Run Tests, Not Just Type Checks

AI-generated code often type-checks perfectly but fails at runtime. The type system does not catch:

  • Incorrect business logic
  • Wrong HTTP status codes
  • Missing database constraints
  • Race conditions
  • Performance problems with large inputs

Always run the test suite after incorporating AI-generated code. Write additional tests for edge cases the AI might not have considered.

The "Looks Right" Trap

AI code looks professional. It follows naming conventions, uses proper indentation, includes comments. This surface-level quality makes it psychologically harder to scrutinize. Be especially suspicious of code that looks perfect — perfection in formatting does not mean correctness in logic.

Security Considerations

Never Paste Sensitive Data into AI Tools

This includes:

  • API keys, tokens, and secrets
  • Customer data (PII, medical records, financial data)
  • Internal infrastructure details (IP addresses, architecture diagrams)
  • Proprietary algorithms or business logic

Even if the AI tool claims not to store data, treat every AI interaction as potentially logged and reviewable.

Review AI-Generated Code for Security Issues

AI frequently suggests insecure patterns:

// AI often suggests disabling security features for "convenience"

// CORS — AI may suggest allowing all origins
app.use(cors({ origin: '*' }));

// eval — AI sometimes uses eval for dynamic code
const result = eval(userExpression);

// SQL — AI sometimes forgets to parameterize in complex queries
const query = `SELECT * FROM ${tableName} WHERE id = ${id}`;

// Crypto — AI may suggest weak algorithms
const token = Math.random().toString(36).substring(2);
// Math.random() is not cryptographically secure

For every AI-generated code block that touches authentication, authorization, data access, or user input, conduct a focused security review.

Company AI Usage Policies

Many New Zealand companies now have formal AI usage policies. Before using AI tools at work, understand:

  • What tools are approved? Some companies only allow enterprise-tier AI tools (GitHub Copilot Business, not the free tier).
  • What data can you share? There may be restrictions on pasting production code, customer data, or internal documentation.
  • Disclosure requirements — some teams require noting when code is AI-generated in PR descriptions.
  • Compliance implications — in regulated industries (finance, healthcare), AI-generated code may have additional review requirements.

If your company does not have an AI policy yet, ask for one. It protects both you and the organization.

Code Ownership and IP

AI-generated code raises questions:

  • License compliance — AI may reproduce patterns from copyleft-licensed code in its training data. If your project uses a permissive license, this could be a problem.
  • Originality — can you claim copyright on code that AI generated? The legal landscape is still evolving.
  • Attribution — some organizations require disclosing AI-generated code in pull requests.

For most day-to-day work, these concerns are theoretical. But for code that is core to your product's competitive advantage, be thoughtful about what AI generates versus what humans write.

Staying Sharp

Understand Before You Ship

The non-negotiable rule: if you cannot explain what a piece of code does and why it is correct, do not commit it.

This means:

  • Read the AI-generated code completely before accepting it
  • Be able to explain the algorithm, the error handling, and the edge cases to a colleague
  • Understand the dependencies and APIs being used
  • Know what would break if the input changes

Code Without AI Periodically

AI assistance can atrophy your core skills if you rely on it exclusively:

  • Whiteboard interviews — you will not have Copilot in a technical interview. If you cannot write a binary search from memory, you have a problem.
  • Debugging without AI — when AI cannot diagnose the problem (and it often cannot for novel bugs), you need raw debugging skills.
  • System design — understanding how systems fit together is a skill that comes from building, not from prompting.

Set aside time to code without AI. Work through algorithm problems. Build small projects from scratch. The fundamentals matter more than the tools.

Use AI to Learn, Not Just to Produce

Instead of just accepting code, use AI as a teacher:

Explain why you chose a Map instead of a plain object here.
What would happen if I used a WeakMap instead?
What are the performance tradeoffs?
You used the builder pattern for this configuration.
What are the alternatives, and when would each be more appropriate?
This solution uses recursion. What is the iterative equivalent?
What are the stack depth implications for large inputs?

AI that explains its reasoning teaches you more than AI that just produces code.

AI Makes You Faster, Not Smarter

A common misconception: "I use AI so I can work on harder problems." The reality is that AI makes you faster at the easy and medium parts of engineering. The hard parts — architecture, trade-off analysis, debugging novel problems, understanding user needs — still require human intelligence and experience.

Invest in understanding, not just output velocity.

Team Practices

Establish AI Usage Norms

Teams should agree on:

  • Transparency — mention in PR descriptions when AI generated significant portions of the code. This is not shameful; it helps reviewers calibrate their review depth.
  • Review rigor — AI-generated code gets the same review as human-written code. No exceptions.
  • Shared prompts — maintain a team wiki or Slack channel for effective prompts. If someone finds a great prompt for generating migration scripts, share it.

AI-Generated Code Still Needs Full Review

A common anti-pattern: "It was AI-generated, so I just did a quick glance." This is backwards. AI-generated code needs more scrutiny, not less, because:

  • The author (the AI) cannot answer questions about intent
  • The AI does not understand your business rules
  • Subtle bugs are harder to spot in well-formatted code
  • The person submitting the PR may not fully understand the generated code

Review AI-generated PRs line by line, just like any other PR.

Pair Programming with AI

An effective pattern for complex tasks:

  1. One person drives the AI — writes prompts, iterates on output
  2. The other reviews in real-time — catches issues as they appear, suggests refinements
  3. Both discuss the design — use AI to explore options, but make the decision together

This combines the speed of AI with the judgment of two humans. It is particularly effective for:

  • Refactoring large codebases
  • Generating comprehensive test suites
  • Exploring multiple implementation approaches for a design decision

Share Effective Prompts

Build a team prompt library for common tasks:

  • Database migration generation
  • Test fixture creation
  • API documentation templates
  • Code review checklists
  • Performance audit prompts
  • Deployment configuration generation

When a team member discovers a prompt that consistently produces good results, add it to the shared library. This compounds the team's effectiveness over time.

When NOT to Use AI

There are situations where AI-generated code is actively harmful.

Security-Critical Authentication and Authorization

// DO NOT ask AI to write your authentication logic.
// Write it by hand. Understand every line.
// Authentication bugs are the most exploited vulnerability class.

// This includes:
// - Password hashing and verification
// - Session management
// - Token generation and validation
// - Permission checks and access control
// - OAuth/OIDC flow implementation

AI may produce authentication code that looks correct but has subtle vulnerabilities: timing side-channels, weak token generation, incorrect permission propagation. Write this by hand, have it reviewed by a security-aware engineer, and use well-maintained libraries (passport, next-auth, lucia) rather than custom implementations.

Performance-Critical Hot Paths

AI does not know your system's performance profile. It cannot:

  • Identify which code paths are hot (called millions of times)
  • Measure the actual cost of allocations, copies, or cache misses
  • Profile your specific workload
  • Understand your latency requirements

For performance-critical code, profile first, then optimize by hand with measurement.

Domain-Specific Business Rules

// AI does not know that:
// - In NZ, GST is 15% and applies to most goods and services
// - Your company gives a 5% discount to customers who have been active for 2+ years
// - Orders over $500 require manager approval but only on weekdays
// - The "premium" tier was renamed to "professional" last quarter
//   but old data still uses the old name

// Business rules are the core of your application.
// They require human understanding of the domain.

AI can implement business rules once you specify them precisely, but it cannot infer them. And if you specify them incorrectly, AI will implement the wrong rules confidently.

When You Are Learning

If you are learning a new concept — a data structure, a design pattern, a framework — do not let AI shortcut the learning process.

The struggle of figuring things out is how you learn. If AI writes your first binary tree for you, you will not understand binary trees. You will understand how to prompt AI.

Use AI to check your work after you have attempted it yourself. Use it to explain concepts you are stuck on. But do not use it to skip the learning entirely.

The Decision Framework

Before using AI for any task, ask:

  1. Do I understand this well enough to review the output? If no, learn first, then use AI.
  2. Is this security-critical? If yes, write by hand or use established libraries.
  3. Does this require domain knowledge AI does not have? If yes, specify the rules precisely or write it yourself.
  4. Is this a learning opportunity? If yes, try first without AI.
  5. Is this well-defined and pattern-based? If yes, this is where AI shines. Use it.

AI-assisted development is a skill multiplier. Like any multiplier, it amplifies both your strengths and your weaknesses. The practices on this page are designed to ensure it amplifies the right things.

On this page