AI has changed the way I write software. I use it every day for understanding unfamiliar code, exploring approaches, debugging, writing tests, reviewing implementations, generating repetitive code, and sometimes just thinking through a problem.
It has made me significantly faster, but it has also made one thing very obvious: AI amplifies the way you already work.
A good developer with strong engineering discipline can use AI to move extremely fast. A careless developer can use the same tool to produce an impressive amount of bad code in very little time.
Most problems I see with AI-assisted development are not really AI problems. They are engineering problems that AI makes easier to create.
Give AI Project Rules
I don’t rely on the default behaviour of whatever coding model I happen to be using. Every serious project should have a shared instruction file explaining how that repository works.
Without one, AI will happily introduce new patterns, duplicate components, install unnecessary packages, change unrelated files, or follow conventions it knows from elsewhere instead of the conventions your project already uses.
My project instructions usually include things like:
- Follow the existing architecture and folder structure.
- Look for reusable components and utilities before creating new ones.
- Don’t add dependencies when existing code can solve the problem.
- Use exact dependency versions.
- Avoid very new packages unless there is a good reason to use them.
- Keep files reasonably small and separate responsibilities properly.
- Don’t refactor unrelated code while completing a focused task.
- Run type checks, tests, linting, and builds before calling the work complete.
- Mention assumptions when requirements are unclear.
- Suggest a proper commit message when the task is finished.
This doesn’t make AI perfectly consistent, but it gets most of the output moving in the right direction.
The important part is that the rules should keep changing. Whenever AI repeatedly does something stupid, add a rule for it. If it keeps duplicating components, changing unrelated files, ignoring an existing abstraction, or installing packages for trivial problems, document that.
Over time, the rules file becomes a collection of mistakes you don’t want repeated.
The Workflow Matters More Than the Model
Teams spend a lot of time debating which model is better. That matters, but not nearly as much as how developers actually use it.
You can give a great model a vague one-line requirement, accept a huge patch without reading it, and get terrible results. You can give the same model the right context, explain the boundaries, work in smaller steps, review its assumptions, and get excellent results.
That is why engineering leads should occasionally work alongside developers and see how they use AI. Not to count prompts or police their workflow, but to understand how they explain tasks, what context they provide, how they recover from wrong answers, and whether they actually understand the code being generated.
A pull request only shows the final result. Watching someone work often exposes the real problem much faster.
Sometimes the developer doesn’t have a coding problem. They have a context, prompting, or review problem.
Stop Keeping Every Task in One Chat
One mistake I made early was keeping AI conversations alive for too long because they already contained project context.
Eventually that context becomes baggage. Old requirements, abandoned approaches, unrelated files, and previous assumptions start influencing new work. The conversation feels useful because it “knows everything”, but the quality often gets worse.
For a reasonably isolated task, I now prefer starting fresh and providing only what is needed:
- Project instructions.
- The actual requirement.
- Relevant files or modules.
- Important constraints.
- Commands used to verify the result.
More context is not automatically better. Relevant context is better.
Dumping an entire repository or months of conversation history into a model is not good context management. Give it enough information to make the decision, not everything you happen to have.
The same principle matters when using expensive models or limited API quotas. Large amounts of irrelevant context cost money without necessarily improving the result.
Keep Important Decisions Outside the Chat
I don’t think every AI conversation needs to be archived. That would just create another pile of useless documentation.
But important engineering decisions should not exist only inside an AI thread.
If AI helped with an architectural decision, a difficult production issue, an unusual implementation, or an important debugging session, keep enough evidence for someone else to understand what happened. That might be a short explanation in the pull request, terminal output, benchmarks, logs, relevant documentation, or the useful part of the conversation.
The point isn’t to preserve prompts for the sake of preserving prompts. The point is to preserve reasoning that matters.
A decision hidden inside an old AI conversation is not useful project knowledge.
You Own the Code
This is the rule I care about most:
You cannot blame AI for code you reviewed, committed, and deployed.
AI didn’t merge the pull request. It didn’t approve the architecture. It didn’t decide the test coverage was sufficient. A developer did.
There is nothing wrong with saying AI generated the first implementation. That’s simply describing the workflow. But “AI wrote it” is not an explanation for a production bug, security issue, broken abstraction, or bad architectural decision.
Once you accept generated code, you own it.
You should understand what it does, why the approach was chosen, what assumptions it makes, what can fail, and how it was tested. If another developer asks about the implementation, you should be able to explain it without opening the original AI conversation and asking the model what its own code does.
This is also why I’m uncomfortable with massive AI-generated changes that nobody can properly explain. They can look like impressive progress while quietly creating a maintenance problem.
Code nobody understands is not finished work.
Finished-Looking Code Is Dangerous
AI has become very good at producing code that looks complete. Naming is clean, structure looks reasonable, comments sound confident, and sometimes everything even compiles on the first attempt.
That makes shallow review much more dangerous.
The implementation can still misunderstand a business rule, miss an authorization check, duplicate existing logic, introduce a race condition, or handle only the exact case described in the prompt.
The better AI gets at producing convincing code, the more important it becomes to review the actual logic instead of being impressed by how polished it looks.
A successful build doesn’t prove the architecture is good. A clean implementation doesn’t prove it solves the right problem. A working demo doesn’t prove it handles production.
AI Is Still a Tool
AI tools also have practical limitations. Models hit quotas, APIs fail, repository context gets too large, output quality varies between tasks, and sometimes the supposedly easy AI-assisted approach stops being easy.
Those constraints should be treated like any other engineering dependency.
If an estimate assumes AI will make a task significantly faster and that stops being true, communicate it. Explain what changed, whether the work can continue manually, and what it does to the estimate.
AI being unavailable can explain a delay. It doesn’t remove the responsibility to communicate one.
The Bottleneck Is Changing
AI isn’t reducing the need for engineering discipline. I think it is doing the opposite.
Typing code is becoming cheaper. Producing code is becoming easier. That means producing too much code, unnecessary abstractions, duplicate logic, random dependencies, and plausible-looking wrong solutions is also becoming easier.
The valuable part of engineering is moving further away from how quickly someone can type an implementation.
It is increasingly about understanding the problem, choosing good boundaries, giving AI the right context, rejecting convincing but incorrect solutions, reviewing architecture, knowing what not to build, and taking responsibility for the result.
I wouldn’t want to go back to developing without AI. It is far too useful.
But it doesn’t make someone a better engineer.
It makes whatever kind of engineer they already are much faster.