How I Use AI to Write Better Code (Without Losing My Skills)
A senior engineer's honest take on AI-assisted development. What actually helps, what's a waste of time, and how to keep your skills sharp while shipping faster.
Is AI going to replace developers? I don’t think so. But developers who refuse to use AI tools will find it harder to compete with the ones who use them well.
There is a bad way to use AI, though. You hand everything to an AI assistant, accept whatever comes back and ship it without reading it. That kind of code is easy to spot in review. It sort of works, but nobody on the team can explain why it’s structured the way it is.
Day to day I use Claude Code for complex work, and design is where I find Claude strongest. A local Qwen model handles everything else. The work itself spans Python microservices, React front ends and AI-driven backend systems. My approach with both tools is simple: let AI handle the repetitive parts and stay hands-on for the parts that matter.
Treat it like a fast junior developer
The mental model that helped me most is to think of AI as a very fast junior developer with no context about your system.
A junior developer can write boilerplate, suggest common patterns, draft basic tests and spot obvious bugs. What they can’t do on their own is understand your whole architecture, make design decisions, know your business constraints or debug a complex distributed system. AI is the same. It speeds up tedious work and struggles with anything that depends on understanding your system.
Where it saves me time
Setting up a new service is mostly repetition: a FastAPI skeleton, config loading, health checks, error handling. Now I describe the structure I want, get most of it back quickly, then review every line and adjust it to our patterns. The business logic is still mine to write.
Tests are another area. I like writing tests, but I don’t enjoy writing twenty variations of the same validation test. AI generates the scaffolding, and I review it, add the edge cases it missed and fix the mocking.
# I write the function signature and core logic myself
async def migrate_user_data(user_id: int, target_schema: str):
old_user = await fetch_old_user(user_id)
# AI helps with the transformation boilerplate
# I handle the edge cases and validation
if not validate_migration_rules(old_user, target_schema):
raise MigrationError("Invalid schema transition")Documentation works the same way. I sometimes write the implementation first and the docstring second. AI drafts it, and I check that it actually describes what the function does and add the context it can’t know. When I’m picking up a new library, AI is good for focused examples to get started, but I still read the official docs.
Where I don’t use it
I don’t hand over architecture decisions. Choosing between microservices and a monolith, SQL and NoSQL, or sync and async needs business context and long-term thinking that a model doesn’t have. AI can explain the tradeoffs, but the decision is mine.
Production-critical logic is the same. For business rules, security code or anything involving payments, I either write it myself or review AI suggestions line by line.
The main reason is that AI takes shortcuts all the time. If you don’t tell it how data should be handled, it will write everything to local storage and move on, or leave API keys lying around in the code. It works, so it looks done. That’s why I spell out data handling and security requirements before it writes anything, and why I still review the result. In systems with real users I check everything.
Complex debugging is another weak spot. When something breaks in a distributed system, AI suggestions tend to be too generic, like asking whether you’ve checked the connection string. Real debugging means understanding your specific architecture, reading traces and knowing how data flows through the system. AI can be useful for explaining a cryptic error message, but the investigation is still my job.
Staying sharp
If AI writes a lot of your code, do you get worse at coding? The risk is real. Autocomplete suggests a line, you accept it and move on, and a week later you can’t remember the syntax for something similar.
So I’m deliberate about it. I still solve problems and build small things without AI, especially the fundamentals like data structures and system design. AI does give bad suggestions, and I need to understand why they’re bad.
When AI suggests something I haven’t used before, I don’t just accept it. I look it up and understand the tradeoffs. For example, if it suggests asyncio.gather() for parallel API calls, it’s worth knowing how that compares with TaskGroup, especially in how each handles errors.
Explaining things to other engineers helps too. Mentoring through code review is a good test, because you can’t fake understanding when someone asks you why. Reading open-source code and building a small project without AI now and then are also good ways to check what you actually know versus what you’ve been outsourcing.
What changed in practice
Boilerplate, documentation and test scaffolding go noticeably faster for me. I haven’t measured it precisely, and I review everything the same way I did before. The gain isn’t dramatic, but it adds up, and that time goes into harder problems and better architecture.
Using AI well isn’t about working less or thinking less. It’s about not spending time on work that doesn’t need your expertise. I use it to ship faster, but I’m still the one making the decisions, reviewing the output and making sure it holds up in production.
Comments
Loading comments