As agentic coding tools have improved dramatically in recent months, it’s clear that software engineers have to adapt. An experienced engineer likened the moment to earlier advances in programming when he asked, “I wonder if this is how the C developers felt when higher-level languages came on the scene: ‘But I want to manage the memory myself! How else will I know if it’s managed properly?’”
I saw myself in this lament. For months I held fast to the belief that if I didn’t at least partially write the code myself, how would I know the ins and outs of it? Surely it was wrong to fully delegate the task of writing code to… a computer?
It has become clear that this is a “change or die” moment. It takes me back to fifth grade when our teacher read Who Moved My Cheese?, from which I took away that one slogan.
All of this has me thinking: what matters in this new world? What attributes will define great software engineers when, realistically, the new coding language is generative AI—and I’m as likely to write my own code directly as I am to manage memory myself?
Here are a few principles guiding me in this admittedly uncertain and unnerving time:
- Developers who work on a codebase still need a strong mental map of it.
- I have to acknowledge that AI writes cleaner, more idiomatic code than I do—and I should leverage that, not avoid it.
- AI can write amazing code, but it has to be prompted to continually iterate and make code cleaner, more readable, more maintainable. It’s my job to guide the AI toward that ideal.
In this post, I’ll share one way I tried to use AI to achieve excellent code while simultaneously teaching myself a skill I think will matter long-term: knowing the difference between excellent and okay code, and pressing for the former.
What I Did
With the help of AI, I explained my conundrum: I wanted to leverage AI to improve my code while learning what makes “great code.” After some back and forth, I landed on a spec that I put in my repo. I’ll attach it for curious minds, but the gist was this: a main prompt that instructed small, incremental changes with clear justification for why each change made the code better, along with the principle behind each proposed change.
The prompt included multiple “phases” of refactoring: readability, idiomatic code, error handling and robustness, typing as a design choice, API design, and testability.
When I had time, I’d pick a directory and go at it with AI. We went phase by phase. Claude reviewed the directory with a single phase in mind—say, readability—then gave me at most five suggestions for improvement, including justification. I chose some or all for it to implement, but had it implement one at a time.
Why? Part of the challenge with AI-assisted coding is that the volume of code generated can exceed my ability to really read and digest every line. By making very small, incremental changes, I kept the scope small enough that I could actually see what changed and understand its utility. Testing was easier too.
I would go through a handful of phases, then push up a PR. The code got a little better, and I learned a lot. Win.
Appendix: The Full Spec
Here’s the document I used to guide the process.
Core Philosophy
- Excellence is incremental — achieved through many small, intentional improvements
- Learning > perfection — every change should teach a transferable Python principle
- Clarity beats cleverness — readability and intent come first
- No big rewrites — refactors should be scoped and reversible
This mirrors how senior engineers improve real production systems.
How to Use This Document
- Work through the review passes in order
- Run one pass at a time on a single file or a small, cohesive module
- Apply only 1–3 changes per pass
- After each pass, capture what you learned
- Repeat over time. The improvements will compound.
Master Review Prompt
Use this framing prompt for every review pass:
You are a senior Python engineer and code reviewer.
Your goal is to help me incrementally improve this codebase toward “excellent” Python.
I am learning, so prioritize small, high‑leverage improvements over large rewrites.
Each suggestion should teach a concrete Python principle or design lesson.
Avoid nitpicks unless they reinforce a broader concept.
Review Passes
Each pass has a clear theme, a focused prompt, and constraints to prevent overload.
Pass 1 — Readability & Intent
Goal: Make the code easier to read, reason about, and explain.
Focus Areas:
- Naming (variables, functions, classes)
- Function size & single responsibility
- Control flow clarity
Prompt:
Review this code for readability and clarity.
Focus on naming, function size and responsibility, and control flow clarity.
Provide at most 5 improvements. For each improvement, explain why it improves clarity and show a small before/after example.
Do NOT suggest architectural rewrites.
Pass 2 — Idiomatic Python
Goal: Replace non‑Pythonic patterns with idiomatic ones where it improves clarity.
Focus Areas:
- Pythonic control flow
- Appropriate use of comprehensions, context managers, exceptions
- Avoiding patterns from other languages
Prompt:
Review this code for idiomatic Python usage.
Focus on Pythonic control flow, appropriate use of comprehensions, context managers, and exceptions, and avoiding non‑Pythonic patterns.
Limit to 5 changes that meaningfully improve idiomatic quality. Explain the Python principle behind each change.
Pass 3 — API & Function Design
Goal: Make function and class interfaces easier to understand, reuse, and test.
Focus Areas:
- Clear contracts
- Argument design
- Return value consistency
- Hidden assumptions
Prompt:
Review function and class interfaces.
Focus on clear contracts, argument design, return value consistency, and hidden assumptions.
Suggest improvements that make the code easier to reuse and test. Avoid internal refactors unless necessary.
Pass 4 — Error Handling & Robustness
Goal: Reduce surprise and improve debuggability.
Focus Areas:
- Explicit vs implicit failures
- Exception choice
- Defensive programming
Prompt:
Review error handling and edge cases.
Focus on explicit vs implicit failures, exception choice, and defensive programming.
Suggest improvements that reduce surprise and improve debuggability.
Pass 5 — Typing as a Design Tool
Goal: Use type hints to clarify intent and prevent misuse (not to achieve 100% coverage).
Focus Areas:
- Where types clarify intent
- Optional vs required values
- Using types to express constraints
Prompt:
Review this code with type hints as a design aid.
Focus on where types clarify intent, optional vs required values, and using types to prevent misuse.
Do NOT fully type the codebase. Only suggest high‑value type additions.
Pass 6 — Testability
Goal: Make the code easier to test without changing behavior.
Focus Areas:
- Hidden dependencies
- Pure vs impure functions
- Tight coupling
Prompt:
Review this code for testability.
Focus on hidden dependencies, pure vs impure functions, and what is hard to test and why.
Suggest small refactors that improve testability without changing behavior.
What to Avoid
Do not ask the AI to:
- “Fix everything”
- “Make this Pythonic” (too vague)
- “Refactor the whole module”
- “Optimize performance” (unless proven necessary)
Large rewrites reduce learning and increase risk.
Guiding Principle
Excellent Python is the result of many small, intentional improvements — not a single rewrite.