Like a lot of developers, I’ve been experimenting with Large Language Models (LLMs) and tools like GitHub Copilot to speed up my day-to-day coding. They’re genuinely useful – but they’re also very easy to misuse.
The biggest lesson so far is simple:
You should drive the AI. Don’t let it drive you.
LLMs are powerful pattern machines, not senior engineers. If you treat them like a thinking partner that needs direction, you’ll get value. If you treat them like an autopilot, you’ll get mess.
Here are my key takeaways so far.
1. You must push back on the AI
LLMs are extremely good at sounding confident, even when they’re wrong.
If you’re not actively questioning suggestions, one of these will happen:
- You’ll accept code that “looks right” but subtly breaks things.
- You’ll slowly lose your own understanding of the codebase.
- You’ll ship patterns that don’t fit your architecture or standards.
The rule I’ve settled on:
Never accept a suggestion you don’t fully understand.
If the AI suggests a refactor, I force myself to answer a few basic questions:
- Do I understand exactly what this code does, line by line?
- Is this idiomatic for our language/framework?
- Does it respect our architecture, naming, and conventions?
- What are the edge cases, and has it missed any?
If I can’t explain the suggestion in my own words, I don’t accept it.
The AI is there to generate options, not decisions. Decisions are still my job.
2. Only give it the context it actually needs
One easy trap is dumping huge amounts of context into the model “just in case”. This often backfires.
Too much context means:
- The model gets distracted by irrelevant details.
- You waste time preparing prompts instead of solving the problem.
- You risk exposing more of your codebase than is necessary (in hosted tools).
Instead, I aim for just enough context:
- The specific method, class, or module I’m working on.
- A short explanation of what this piece is supposed to do.
- Any constraints that really matter (performance, backwards compatibility, etc).
I rarely give it the entire world. If it needs more, I add it gradually.
Think of it like pairing with a junior dev: you don’t start by handing them the entire codebase and a 30-page architecture doc. You give them the slice that matters, plus any assumptions they must not break.
3. LLMs don’t know your monolith’s history
This is a big one for working in a mature monolith.
Even if you plug in code search, embeddings, or nice integrations, the AI still doesn’t truly understand:
- Why that weird edge-case path exists.
- Which compromises were made to keep legacy behaviour.
- Which feature flags are sacred.
- Which parts of the app are brittle because of historical decisions.
It only sees patterns in text. It doesn’t know the politics, the tech debt, or the subtle “if you touch this, ops will scream” hotspots.
So when an LLM suggests a big tidy-up or a “much cleaner” abstraction, I’ve learned to be suspicious:
- What migration path is needed for this change?
- Will this break reports, exports, or integrations that aren’t obvious from this file?
- Is there a reason this wasn’t done years ago?
In a mature system, local neatness can be global chaos. The AI can help propose neater local code, but it has no context for the rest of the ecosystem unless you explicitly give it that.
You still have to be the one who remembers (or investigates) why things are the way they are.
4. I’m not fully integrated yet – and that’s fine
Right now, I’m not at the stage where the AI is deeply wired into every part of my workflow. I’m using it more like:
- A smart autocomplete for boilerplate.
- A code explainer when I’m reading something unfamiliar.
- A brainstorming partner for alternative approaches.
- A helper for tests, docs, and small refactors.
But I’m not doing:
- Automated large-scale refactors driven entirely by AI.
- Letting it design major architectural changes.
- Blindly trusting it with performance-critical or risk-y areas.
I think that’s actually a healthy place to be. Incremental adoption means I can build intuition for where it helps and where it hurts, without handing over the steering wheel.
5. Greenfield would be a different story
All of this is very different to what I’d do on a greenfield project.
If I started something from scratch tomorrow, I’d be far more aggressive about using LLMs to:
- Sketch initial architecture and folder structure.
- Generate the first version of boring glue code.
- Spin up scaffolding: models, endpoints, serializers, tests.
- Explore different implementation approaches quickly.
The risk surface is much smaller in a new repo. There’s no decade of legacy, no silent dependencies, no hidden consumers of that random method.
You can let the AI generate more, then iterate. The worst case is usually: “We delete it and try again.”
In a monolith, the worst case might be “We broke billing for a subset of customers from 2018 who use an old flow nobody remembers.”
6. Delegating tasks to Copilot & friends
I’ve found it helpful to delegate specific types of work to AI tools, rather than just “use AI more”.
Examples:
- Boring repetition
- Generating similar test cases with different inputs.
- Expanding a pattern across multiple files.
- Mechanical transformations
- Converting one style of query or validation to another.
- Migrating from one API wrapper to a new one (in a controlled, reviewed way).
- Explaining unfamiliar code
- “Explain what this method is doing and list potential edge cases.”
- Scaffolding docs and comments
- Drafting docstrings, README sections, or ADR summaries that I then edit.
- Drafting docstrings, README sections, or ADR summaries that I then edit.
I think of it as having a tireless junior who can type 200 wpm and never gets bored – but still needs careful review and clear instructions.
7. Practical guardrails that help
A few things that have helped me stay on the right side of “AI assisted”, rather than “AI driven”:
- Review AI code as if it came from a colleague you don’t fully trust yet.
No exceptions, even for “tiny” changes. - Keep changes small.
Ask the AI for narrow edits, not giant multi-file rewrites. Easier to test and reason about. - Run tests early and often.
Don’t rely on “it compiles” as a signal that things are OK. - Be explicit about constraints in your prompts.
For example: “Don’t change the public interface”, “Don’t touch this method”, “Must be backwards compatible”. - Use it to learn, not to outsource thinking.
Ask “why” and “what if” questions. Make the AI justify its approach.
Conclusion: Power tool, not autopilot
LLMs and tools like Copilot are here to stay. Used well, they:
LLMs and tools like Copilot are here to stay. Used well, they:
- Speed up the boring parts.
- Help you explore more options.
- Act as an always-available sounding board.
Used badly, they:
- Smuggle bugs into your codebase.
- Erode your understanding of your own system.
- Encourage cargo-cult patterns that don’t fit your architecture.
The difference isn’t in the tool. It’s in how you use it.
Drive the AI. Question it. Constrain it. Learn from it.
But never let it quietly take the wheel. That’s still your job.
But never let it quietly take the wheel. That’s still your job.