AI Took Over Tactical Code; Strategic Software Engineering Is What Remains
The Most Valuable Thing in the AI Coding Era: Software Engineering (Part 2)
Author: 乘风gg Tags: Frontend, Claude, AI Programming
The previous post covered the codebase, why AI turned it from an aesthetic problem into a hard metric, and how to maintain a good codebase. That post opened with four failure modes: alignment failure, verbosity, code that would not run, and building a mess. The fourth is the most serious, and it was covered in the previous post. This one handles the remaining three, plus the premise they all share: which parts humans should manage, and how the work should be split.
In this post, this repo refers to Matt Pocock's open-source skills, github.com/mattpocock/skills, a set of skill packages installed into tools like Claude Code and Codex and invoked with /commands.
AI eliminated the tactical work; strategic work remains
Writing code splits into two kinds of work.
One is tactical work: fixing a local bug, adding a field, correcting a function. AI is good at this kind; I estimate it handles more than 90% of it.
The other is strategic work: whether a module should exist, where its boundaries are, where complexity should be hidden. AI cannot do this kind, and it won't close the gap soon: it excels at winning a battle, not at thinking about what matters three months from now.
The two kinds also differ in a crucial way: how quickly you can tell you got it wrong. A tactical mistake is known within ten minutes; a strategic mistake, such as tangled data flow, might take nine months. The only place AI can help with strategy is making this feedback faster.
Another angle makes it clearer: knowledge and wisdom. Knowledge is syntax, APIs, and how things work; wisdom is judgment, trade-offs, and when not to use something. Knowledge is now cheap to the point of being nearly free; wisdom is still rare. And what people are actually willing to pay for is the latter.
So: AI has finished the fast work, and what remains is all slow work, the kind you never practiced much.
Do one small piece at a time, but make it run every time
First: how to split the work.
AI has a natural tendency in coding: it likes to work breadth-first, first create all the database tables, then write all the APIs, then build all the pages, because large models naturally think in layers. The cost: what you review is always a pile of half-finished pieces. Each layer looks right in isolation, but whether they fit together is unknowable until the end.
How do you fix breadth-first work? There is an established name: tracer bullets. In WWII, anti-aircraft gunners mixed a glowing tracer round into every five rounds to watch the trajectory and adjust their aim. The Pragmatic Programmer brought this into software engineering more than twenty years ago.
This repo's answer is exactly that: keep each piece as small as possible, but make it pass through every layer completely (database, API, page, tests), a single path working end to end, so it can demo itself and verify itself. It is like an MVP version of the code, with an AI-era constraint: the MVP must fit into one fresh context window.
Some cases are exceptions: some changes inherently cannot be split. For example, changing a field's type once you do, thousands of call sites break. It has to be done in three steps, layer by layer: add the new one (while keeping the old one), migrate call sites in batches, then delete the old one.
Solutions for the first three failure modes
Failure mode one: before starting, make it ask you questions
The first failure mode is: you think you explained clearly; it thinks it understood.
The trouble is that AI does not ask on its own. A human raises doubts on the spot; it doesn't. It takes a half-understood requirement and writes all the way to the end, delivering something syntactically correct but not what you had in mind.
The two most popular skills in this repo, grill-me and grill-with-docs, do the same thing: make it ask you questions first, forcing you to spell out who it is for, under what conditions it is not done, and where the boundaries are. After one round, the vague areas shrink by more than half. It asks the questions, but you keep the direction: it just asks more granularly than you would yourself, and the trade-offs remain in your hands.
Failure mode two: build a shared vocabulary
The second failure mode is verbosity: a concept takes twenty words to explain.
The fix is not to make AI talk less, but to build a team-wide glossary: give each awkward business concept a definitive name, write it into a file, and put that file in the project, shared by people and AI.
This is not a new method invented for AI. It has long existed in software engineering, called Ubiquitous Language, from Eric Evans's Domain-Driven Design from twenty-plus years ago (the book that established what everyone calls DDD): business and technology speak the same vocabulary; whatever it is called in requirements meetings is what it must be called in code.
Back then it saved communication cost between people. With AI, it saves one more: the cost between people and AI.
The format this repo defines for CONTEXT.md is two lines per entry:
**Order**: the purchase request submitted by the user.
Don't use anymore: slip, form, transaction
The first line gives it a canonical name; the second line invalidates the alternatives. Previously, describing this required a roundabout explanation, and the team used different words, product said slip, backend said transaction, frontend said order, and AI read them as three different things. Once defined, people and AI are left with one word.
One word says it all; no need for a half sentence of explanation.
It also has another use, more important than saving words: it moves how the business actually works out of a few people's heads and into a file. The glossary itself is documentation, and it is the kind AI can read.
Failure mode three: build a feedback loop
The third failure mode is the code would not run: it was written and nobody ran it. The solution is a feedback loop, and everyone knows this. But two things are worth calling out separately.
First write a test that fails
In testing, the most valuable thing is not coverage but order: write a test that fails first, then write the code that makes it pass. In test-driven development (TDD), these two steps are called red and green.
This has two purposes.
One is cheating prevention. AI is best at giving you something that looks right and explaining it confidently. But if a test does not pass, it does not pass; red is red. This is one of the few places it cannot fool you.
The second is proving the change actually took effect. Even if you do not follow full TDD, it is worth demanding evidence: without this change, the test fails.
There is an anti-pattern to avoid.
Do not treat the code's answer as the expected answer. If the correct answer is whatever the code calculates by itself, that test will be green forever. For example, the code says the tax rate is 13%, and the test asserts the result should equal 100 times the tax rate; the tax rate on the right side is the one from the code. If someone later makes a typo and changes it to 6%, the right side changes to 6 too, and the test stays green.
The correct answer must come from outside: calculate it by hand, take a known correct result, or write it from the requirement.
One test, a bit of implementation; walk back and forth.
Use two sessions for writing and reviewing code. In the same session, the context was laid down by the model itself: it continues along its own freshly written line of reasoning and will not go back to overturn itself. If you ask it what's wrong with this code, nine times out of ten it answers nothing. In a clean session, that baggage is gone, and only then does it start acting like a picky reviewer. So the writing session is only responsible for turning the test green; the reviewing session does refactoring and gatekeeping, and coding standards are only sent to the reviewer, since the writer's context window is nearly full anyway.
One final counterintuitive point: the checks humans find annoying are just right for AI. Waiting three minutes for a pre-commit run (the set of checks that run automatically before committing) feels annoying to a human; AI does not care, it was waiting anyway. Those checks that were never added because people found them annoying can now be added back.
The same rule: put it in lint checks or in CLAUDE.md
The same constraint, placed in lint (code style checks) versus CLAUDE.md (project instructions sent to AI with every request), has very different costs.
| In lint rules, type checks, pre-commit hooks | In CLAUDE.md | |
|---|---|---|
| When it passes | Cost is zero: it never enters the AI's context window | Burns tokens on every request |
| When it fails | The error message is pushed back in, forcing it to fix | Probabilistic; it may simply ignore it |
| In one sentence | If it does not pass, it does not go through | Pays to remind every time, and may still be ignored |
So my habit is: anything that can go into lint rules should not go into CLAUDE.md.
Summary
As you become more familiar with AI coding, you will find that AI is just a tool. The tool changed; the method did not.
The craft of architecture never changed; it just now serves one more client: a subordinate with no memory, no taste, but extremely fast hands and strong abilities.
Of course, software engineering and architecture thinking are not limited to what is above. I collected a few more; feel free to look them up if you are interested:
If you have good architecture ideas or AI coding approaches, you are welcome to discuss them in the comments!
You can follow my WeChat official account: 深入浅出AI
Top 1 from juejin.cn, machine-translated. The original thread is authoritative.
Not bad, very insightful.