Yeda AI Knowledge Base

AI-Assisted Coding

Practical techniques for shipping real software with AI coding agents.

Español

← All categories

Tip #001

ADRs Are AI Agent Fuel

Architecture Decision Records give AI coding assistants the one thing your code can’t: why you chose what you chose.

Tip #002

The 5-Second AI Hallucination Detector

AI-generated code can call APIs that never existed. A type checker in pure-check mode catches it in seconds.

Tip #003

Run Two AI Agents at Once with Git Worktrees

Two agents in one folder overwrite each other’s files. Git worktrees give each its own directory and branch on the same repo.

Tip #101

Review the Tests First, Split Big Diffs

Read the tests before the implementation to learn intent first — then keep diffs near 100 lines and split anything approaching 1,000 by stacking, file group, or vertical slice.

Tip #102

Big Refactor? Write a Codemod, Not Hand Edits

When a refactor touches hundreds of lines, stop hand-editing — write a codemod, sed script, or AST transform so one rule applies everywhere and the diff stays reviewable.

Tip #103

That Ugly Code Is Load-Bearing

Chesterton’s Fence for AI-assisted refactoring: run git blame and answer 3 questions before you let an agent delete code it doesn’t understand.

Tip #104

Early Returns Kill Nesting, Named Constants Kill Magic Numbers

Two thirty-second refactors that make AI-generated code readable: flip nested ifs into guard clauses, and pull unexplained numbers into named constants.

Tip #105

Never Cast an Env Var to Bool

bool(\“false\”) is True — a raw cast silently inverts your kill switch. Parse booleans through one shared true/false vocabulary for env vars and config alike.

Tip #106

A Malformed Config Value Should Log and Fall Back

Route every setting through one typed reader that logs a bad value and degrades to a documented default — so a typo becomes a log line, not an outage.

Tip #107

Result And Wait Are Deadlocks Waiting

Why blocking on .Result or .Wait deadlocks C# web apps under load — and how to go async all the way down, with cancellation tokens where they matter.

Tip #109

Your Site Screams AI-Generated: Grep the Three Tells

The AI-slop look is greppable: purple gradients, centered-everything, one uniform 16px+ radius — three commands find it, three fixes break it.

Tip #110

outline: none Without a Replacement Focus Style

One line of CSS locks keyboard users out of your app — how :focus-visible gives them back a focus ring without cluttering the mouse UI.

Tip #111

Server State Belongs in a Cache Layer, Not Global State

Backend-owned data doesn’t belong in your global store — put it in a data-fetching cache layer and get caching, dedup, and invalidation for free.

Tip #112

AI-Generated UIs Skip the Empty States — Prompt for Every One

AI builds your UI’s happy path; users live on the sad path. A checklist for prompting loading, empty, error, and overflow states — every single time.

Tip #113

Never Launch a Goroutine Without an Owner

Goroutines are not garbage collected — every `go` statement in AI-generated Go code needs a cancellation path, a channel owner, and a WaitGroup, or it leaks until the service falls over.

Tip #115

Use ?? For Defaults, Not ||

Why `config.port || 3000` silently discards 0, empty string, and false — and how nullish coalescing (`??`) only falls back on null/undefined.

Tip #116

forEach Doesn’t Await Anything

Why an async callback inside forEach silently races ahead — and how to choose between for...of + await and Promise.all deliberately.

Tip #117

Reject Double Bang in Generated Kotlin

Why every !! in AI-generated Kotlin is a deferred crash, and how explicit nullables and sealed results make the empty case impossible to skip.

Tip #118

Ban GlobalScope.launch in Code Reviews

Unscoped coroutines leak work and swallow cancellation — make GlobalScope.launch an automatic review reject and keep every coroutine tied to an owner.

Tip #121

That Innocent `if exists` Check Is a Race Condition

Why check-then-act filesystem code is a TOCTOU race, and how one parameter — exist_ok=True — deletes the bug and a line of code at the same time.

Tip #122

sum() Streams Lazily, sum([]) Builds First

Drop the square brackets inside sum() and a million-item memory spike becomes a lazy stream — same result, one character less.

Tip #123

Two Ifs In A Comprehension? Give It A Function Name

When a list comprehension grows a second condition, expand it into a named generator function — readable, testable, and a pattern your coding agent will copy.

Tip #124

Threads Won’t Fix CPU-Bound Python

Threads or asyncio for I/O-bound work, ProcessPoolExecutor for CPU-bound — the one-line decision AI assistants get wrong, and how to check which bound you’re fighting.

Tip #125

String += In A Loop Is O(n²), Use Join

Why += on strings inside a loop is quadratic, and the two linear fixes — ''.join() on a list of parts, or io.StringIO for streaming builds.

Tip #126

Always Chain Exceptions With 'from e'

Re-raising a wrapped exception without 'from e' buries the original traceback — two words keep the stack trace pointing at the real cause.

Tip #127

Protocol Over Inheritance: Structural Test Seams in Python

Use typing.Protocol to swap real dependencies for test fakes with zero base-class coupling — structural typing the checker still verifies.

Tip #130

Reject AI-Generated Rails Callbacks That Do Slow Work

AI assistants love after_save callbacks that call APIs — one review rule keeps saves fast, tests offline, and slow work retrying safely in jobs.

Tip #131

Enforce Uniqueness in the Database, Not Just the Model

Rails' validates_uniqueness_of is race-prone under concurrent load — keep it for friendly errors, but add a database unique index for the guarantee.

Tip #132

Every unsafe Block Needs a Safety Comment

An unsafe block suspends the compiler’s checks — write a // SAFETY comment stating the invariant it upholds, and let reviewers and AI agents verify instead of guess.

Tip #133

Never Build Shell Commands As Strings

One unquoted variable can delete the wrong directory. Use Bash arrays for argument lists, quote every expansion, and start scripts with set -euo pipefail.

Tip #134

Swap Offset Pagination for Keyset Pagination

On a large, changing table, LIMIT/OFFSET silently skips and repeats rows — keyset pagination pages by the last row’s key instead and stays consistent while the table mutates underneath you.

Tip #135

Audit AI-Generated Swift for Task.detached

See Task.detached in AI-generated Swift? Stop and check it — a detached task silently drops cancellation, priority, and actor isolation, and it’s almost always the wrong default.

Tip #136

Give Every Suppression an Expiry Date: ts-expect-error over ts-ignore

@ts-ignore stays silent forever; @ts-expect-error errors the moment the underlying bug is fixed, so type suppressions can’t rot in your codebase.

Tip #55

Comment The Why, Never The What

Delete the comments that restate your code and keep the one kind that never rots — why-comments that record intent for humans and AI assistants alike.

Tip #56

Never Leave Commented-Out Code

Delete dead code instead of commenting it out — git remembers every line, and your AI assistant reasons better over files with zero graveyards.

Tip #57

Keep a Rules File for Your Agent

Document your conventions once in a rules file — CLAUDE.md, AGENTS.md, .cursor/rules — and every AI session follows them without you retyping a thing.

Tip #58

Document Gotchas Inline, Where They Bite

The bug that cost you a day belongs as a comment right where the next person will hit it — not in a wiki nobody opens.

Tip #59

Docs Are Executable Context Now

Nobody read your docs — until your coding agent started reading every word. Why decision records are the highest-leverage context you can write, and how to keep one in 10 minutes.

Tip #60

The ADR Alternatives Section Is Gold

Record the options you rejected — pros, cons, and one line on why each lost — so your AI coding agent stops re-proposing ideas you already ruled out.

Tip #61

Onboard AI To A New Codebase Like A Human

Give your coding agent the same onboarding a new hire gets — a fixed five-step recon order plus one traced request — so its code matches your conventions.

Tip #62

Trace One Real Request End-To-End

The fastest way to understand any codebase: follow one real request from the entry point to the database and back — then hand that map to your AI agent.

Tip #63

Record the Local Dialect So AI Code Blends In

A 3-item conventions checklist — naming, patterns, house rules — that makes AI-generated code match your codebase instead of a generic default.

Tip #64

The SMIG Rule: Situation, Mechanism, Implication, Gotcha

A four-part rule for onboarding docs and code tours: skip what the code shows, spend your words on implications and gotchas.

Tip #65

Don’t Dump A File Tree And Call It Onboarding

A folder listing tells your coding agent what exists, not what to act on — replace the raw dump with five narrated moves that trace the real path through the system.

Tip #66

Tour Files: Persona-Targeted Code Walkthroughs

Turn onboarding into a guided route: .tour files anchor step-by-step walkthroughs to real files and lines, one tour per persona, played back inside the editor.

Tip #67

Verify Every Claim Against the Code

READMEs drift; the code doesn’t. How to onboard to any repo by tracing one real request end to end and verifying every claim against source.

Tip #68

Run Your Native Test Suite Under Sanitizers

Why ASan, UBSan, and TSan catch the C/C++ bugs a green line-coverage report ships — and how to wire them into the tests you already have.

Tip #69

Trap Every Crash Before You Fix It

Write the failing regression test before the fix, run it under a sanitizer, and the bug can never silently come back.

Tip #70

Don’t Trust In-Memory Database Tests

In-memory database fakes skip SQL translation — the query that passes in tests can still break in production. When and how to test against the real engine.

Tip #71

Assert All Four UI States

Most widget tests only check the happy render — here’s how to assert loading, empty, error, and success, with the mocking patterns that make each state reachable.

Tip #72

Never waitForTimeout — Wait for the Actual Network

Fixed sleeps make E2E tests slow AND flaky at the same time — replace every one with a wait on the real condition: a network response or an auto-retrying assertion.

Tip #73

Make a Flaky Test Fail Ten Times on Purpose

Reproduce a flaky test locally with --repeat-each=10 (and its pytest/Go equivalents) instead of burning a week of CI retries.

Tip #74

Inject Clocks, Random Seeds, and Temp Dirs

Flaky tests almost always read a hidden input — the real clock, a shared RNG, or a common temp dir. Inject all three and the same inputs give the same result on every run.

Tip #75

Slice Tests, Not a Full Boot

Why @SpringBootTest should be the exception, not the default — use @WebMvcTest and @DataJpaTest slices for faster feedback, and teach your AI assistant the same habit.

Tip #76

Shuffle Your Test Suite to Expose Order-Dependent Tests

Run your suite in random order to surface shared-state bugs on your machine — with the exact shuffle and seed flags for Vitest, Jest, pytest, Go, and RSpec.

Tip #77

A Test That Passes Against Empty Code Tests Nothing

If your suite stays green after you delete the production code, it asserts nothing. The five-minute audit, the placeholder smells, and the tools that automate the check.

Tip #78

Never Launch Real Coroutines in Tests

Why real dispatchers make async tests flake in CI — and how injected TestDispatchers, virtual time, and full Flow lifecycle tests make them deterministic.

Tip #79

One Fixture, Three Databases

Parametrize one pytest fixture across SQLite, PostgreSQL, and MySQL and every test that uses it runs once per backend — 3x coverage from one decorator.

Tip #80

Tag the Slow Tests, Get Your Inner Loop Back

Use pytest markers to split slow integration tests from fast unit tests, so every save runs in under a second while CI runs everything.

Tip #81

Always Mock With Autospec True

A plain Mock says yes to methods that don’t exist — patch with autospec=True so your tests fail when AI-generated code misuses a real API.

Tip #82

Async Mocks: Assert Awaited, Not Called

assert_called_once passes even when your code forgot the await — assert_awaited_once is the assertion that actually catches the bug.

Tip #83

Never Disable Warnings in Pytest

Deprecation warnings are early failure signals — why silencing pytest warnings trades a noisy test run today for a production break tomorrow, and how to filter with precision instead.

Tip #84

pytest --lf: Re-run Only What Broke

Shrink your debug loop from minutes to seconds with pytest --lf, -x, --ff, and --sw — and why it matters even more when an AI agent is doing the debugging.

Tip #85

100% Test Coverage Is a Red Flag, Not a Badge

Why chasing 100% coverage suite-wide wastes effort — target ~80% overall and push auth, payments, and data mutation toward 100% branch coverage instead.

Tip #86

Test Background Jobs for Idempotency and Retries

Queues deliver at-least-once, so every job will eventually run twice — here’s how to test that a duplicate run charges once, emails once, and recovers cleanly after a crash.

Tip #87

Flaky Tests Trace To Time, IO, Or Randomness

A test that passes sometimes almost always touches real time, real files, or real randomness — inject all three and the flake disappears.

Tip #88

Make Docs Executable: Doc Tests

Compile the examples in your docs as part of the test suite — Rust doc tests, Python doctest, and pytest patterns that make stale documentation fail CI.

Tip #89

Run Shell Scripts Through ShellCheck First

Lint AI-generated bash with ShellCheck before you test it — most shell bugs are static quoting and portability issues a linter catches in seconds.

Tip #90

Test Shell Scripts From a Path With Spaces

Run your shell scripts from a temp directory whose path contains a space — quoting and cwd bugs break loudly, for free, before they break in production.

Tip #91

Test Data Invariants at the Database Layer

Why app-layer validation isn’t enough — prove unique keys, foreign keys, and check constraints on a real, disposable database that matches production.

Tip #92

Fake the Network, Persistence, Clocks — and Permission State

You already fake the network and the database in tests — inject a fake for permission state too, so the denied branch stops shipping broken.

Tip #93

Prove The Bug Before You Fix It

Make your AI coding agent write a failing test that reproduces the bug before touching the fix — proof it exists, proof it’s gone, and a permanent guard.

Tip #94

Have a Separate Subagent Write the Reproduction Test

Split the reproduction test from the fix — a fresh subagent writes the failing test without seeing the fix, so shared blind spots can’t rubber-stamp a broken patch.

Tip #95

The Tautology Mock

When a mock returns the exact value the test asserts, the test passes with zero production code running — how to spot tautology mocks and replace them with real implementations or in-memory fakes.

Tip #96

Keep Tests DAMP, Not DRY

Why duplication is fine in tests: DAMP (Descriptive And Meaningful Phrases) beats DRY when a failing test has to explain itself.

Tip #97

Have the Agent Discover the Project’s Own Commands

Stop your AI agent from guessing pytest or npm test — make it read package.json, pyproject.toml, the Makefile, and CI to find the repo’s canonical commands.

Tip #98

Never Trust AI-Written Tests To Validate AI-Written Code

When one model writes both the code and its tests, they can share the same wrong assumption — a green suite proves consistency, not correctness. Here’s how to break the loop.

Tip #99

A Raw Owning Pointer in C++ Is a Review Blocker

Why a raw owning pointer should fail C++ code review — and the RAII checklist (unique_ptr, shared_ptr, span, rule of zero) to enforce on AI-generated code.

Tip

Check context.mounted After Every await

Why Flutter apps crash after slow async calls — and the one-line context.mounted guard that fixes use-after-dispose for good.

Tip

One Model Writes, a Different Model Reviews

Why the model that wrote your code can’t be trusted to review it — and how a two-model write/review loop with a human final call catches far more bugs.

Tip

Java Optional Is a Return Type Only

Optional was designed for one job — method return types. Why fields, parameters, and unchecked .get() fight the design, and the patterns to use instead.