Best Practices for Working with AI Coding Assistants: Avoid Bloat and Stay on Task
Purpose
After using AI coding assistants for a few weeks, I noticed my projects becoming harder to manage. The code worked, but it was bloated. The AI kept adding things I didn’t ask for. And when I tried to make changes later, I couldn’t remember what half the code did.
This post shows the practices I developed to keep AI-generated code maintainable.
The Problem
When I first started using AI coding assistants, everything felt magical. I’d describe what I wanted, and code would appear. But after building a few projects this way, patterns emerged:
Week 1: "Wow, this is fast!"
Week 2: "Hmm, there's some extra code here I didn't ask for..."
Week 3: "Why does my auth module import a payment processing library?"
Week 4: "I have no idea what this project does anymore."I saw this pattern play out repeatedly. On Reddit’s r/codex community, experienced developers echoed the same concerns:
“I felt like that at first, but it fades over time. Now I spend more time fighting bloat and keeping it on task.”
“Codex will do a lot of good work, but it will also introduce a lot of almost correct bugs.”
“After about 100k LOC it fell apart and was just consuming too many tokens.”
The core issue: AI assistants need active management. Without it, they create technical debt faster than manual coding.
Practice 1: Define Scope Boundaries
I used to give AI assistants open-ended prompts:
Build a complete e-commerce system with cart, payments, inventory, admin panel, customer portal, and analytics.The result was predictable: code everywhere, half-implemented features, and imports I never requested.
Now I explicitly state what’s in scope and out of scope:
Build the shopping cart module:- Add/remove items- Calculate totals with tax- Persist to localStorage- Include unit tests
Scope: Cart only. We'll do checkout in next session.Do NOT add payment processing or inventory management.The key changes:
- One module at a time
- Explicit “Do NOT” list
- Clear boundary for next session
Practice 2: Review Code Immediately
I used to accept AI output, move on, and accumulate unreviewed code. This was a mistake. Those “almost correct bugs” the Reddit user mentioned? They compound.
Now I review each component before moving forward:
AI generates code ↓I read every line ↓I test locally ↓I fix issues immediately ↓Only then: next componentThis slows down the initial velocity but dramatically improves maintainability. I catch:
- Unused imports
- Functions that almost work (edge cases fail)
- Code that contradicts my architecture
Practice 3: Ask Before Implementing
When I ask AI to implement directly, I sometimes get solutions that work but are architecturally wrong. The code runs, but it’s built on shaky foundations.
Now I use a two-step process:
I need to build user authentication. Before implementing, explain your proposed approach:- What libraries will you use?- How will you structure the auth flow?- Where will tokens be stored?- What edge cases will you handle?The AI responds with its reasoning. I review and approve or redirect:
Your approach looks good. Now implement it, following these constraints:- Use the existing database connection- Match the error handling pattern in src/errors.js- Include tests for the login edge case you mentionedThis catches architectural mistakes before code exists.
Practice 4: Use Modular Prompts
I mentioned this in Practice 1, but it deserves emphasis. Modular prompts prevent bloat.
The pattern:
┌─────────────────────────────────────────┐│ Session 1: Storage Layer ││ - Define schema ││ - Create migrations ││ - Test CRUD operations ││ ✓ Done, reviewed, tested │└─────────────────────────────────────────┘ ↓┌─────────────────────────────────────────┐│ Session 2: API Layer ││ - Define endpoints ││ - Add authentication ││ - Test with storage layer ││ ✓ Done, reviewed, tested │└─────────────────────────────────────────┘ ↓┌─────────────────────────────────────────┐│ Session 3: UI Components ││ - Build forms ││ - Connect to API ││ - E2E test critical paths ││ ✓ Done, reviewed, tested │└─────────────────────────────────────────┘Each session is complete. Each session is reviewed. No accumulation of unreviewed code.
Practice 5: Maintain an Architecture Reference
One Reddit user noted: “Unless its a small project, I find AI vibed projects to be kinda useless since I eventually dont know enough to be able to quickly validate changes myself.”
The solution is an architecture document that the AI references:
# Project: Task Manager App
## Modules (build in order):1. Storage layer (local DB) - DONE2. Task CRUD API - DONE3. UI components - IN PROGRESS4. Sync service - NOT STARTED
## Tech decisions:- SQLite for local storage (not MongoDB)- React components (not Vue)- No real-time sync (batch sync only)
## Out of scope:- Mobile app- Team collaboration- External integrationsNow every prompt includes: “Refer to ARCHITECTURE.md for context.”
The AI:
- Knows what’s already built
- Follows existing tech decisions
- Doesn’t suggest out-of-scope features
I update ARCHITECTURE.md after each session.
Practice 6: Build Tests with the Code
I used to ask for tests after building features. The AI would write tests that passed for the happy path but missed edge cases.
Now I ask for tests with the implementation:
Build the user registration module:- Validate email format- Check password strength- Store hashed password- Include unit tests for all validation functions- Include integration test for the registration flow
After building, show me:1. The implementation2. The tests3. Which edge cases each test coversThis catches “almost correct bugs” immediately. The tests document what the code should do, which helps me validate the AI understood my requirements.
Common Mistakes I Made
Accepting All Output Without Review
I trusted the AI too much. The code compiled, the tests passed, so I moved on. But tests written by AI often miss the same edge cases the implementation misses.
Building Entire Systems in One Prompt
“Build a social media app” produces unmaintainable garbage. I learned to build one module at a time.
Not Maintaining Architecture Documentation
Without ARCHITECTURE.md, the AI forgets what it built in previous sessions and introduces inconsistencies.
Skipping Tests Because “AI Handles It”
AI-generated tests follow the same assumptions as AI-generated code. When both are wrong, they’re wrong together. I need to review tests as carefully as implementation.
Accumulating Code Without Understanding It
This is the trap. AI velocity feels good until you need to debug. If I can’t explain what the code does, I shouldn’t commit it.
Why This Matters
AI coding assistants amplify both productivity and technical debt. Without management, the debt grows faster than productivity.
The practices I’ve described are essentially discipline. They slow down the initial velocity but prevent the Week 4 realization that you don’t understand your own codebase.
The Reddit comment that stuck with me: “It augments devs and still needs to be reviewed and tested by a human.”
That’s the key. AI is an augmentation, not a replacement. The management practices ensure the augmentation helps rather than hurts.
Summary
In this post, I showed six practices for managing AI coding assistants: scope boundaries, immediate review, asking before implementing, modular prompts, architecture documentation, and building tests with code.
The key takeaway: Active management prevents AI-generated code from becoming unmaintainable debt. Review everything, build incrementally, and never commit code you can’t explain.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments