← All notes

AI can write the code. You still own the change.

AI helps with implementation. The assumptions, verification, and consequences still need an engineer.

  • ai
  • engineering
  • architecture

I use AI to help write code. It’s useful for implementation, refactoring, tests, and working through code that needs a closer look.

But when that code goes into a system I’m responsible for, it’s my change. I don’t get to hand the responsibility back to the tool because it produced the implementation.

The code can be reasonable and still be wrong

A function can look fine on its own. Clear names, sensible structure, nothing obviously broken. That doesn’t tell me whether it belongs in this system.

Maybe there’s already a place where that behavior lives. Maybe the proposed change bypasses a permission check that happens somewhere else. Maybe it adds a dependency for something the application already knows how to do.

Those aren’t always problems you can spot by reading the new function. You have to follow the surrounding code. Find the callers. Understand where the data comes from and what the rest of the application assumes about it.

I can ask AI to help with that too. But I still need to check what it found. A confident explanation isn’t evidence that it followed every relevant path.

Tests need a little skepticism

Generating tests alongside an implementation is useful. It also creates an easy trap: both can agree on the same wrong assumption.

Say a change is supposed to prevent a duplicate transaction. The implementation checks for an existing record before inserting one. The tests call it twice, one call after the other, and pass.

Good. But what happens when both requests check before either one inserts?

The test covered the sequential case. It didn’t establish the behavior I actually needed under concurrent requests.

That’s a hypothetical example, but it’s the kind of question that matters in review. Not just whether there are tests. Whether they would catch the mistake we’re worried about.

For a bug fix, I want to see the test fail without the fix. For new behavior, I need to know where the expected result came from. If it came only from reading the generated implementation, we may just be testing that the code does what the code does.

A bigger diff isn’t free

AI makes it easy to produce more code than the change needs. Fix the issue, clean up nearby functions, introduce a helper, reorganize a few files. It can all sound reasonable.

Now there’s more to review, and it’s harder to separate the required change from the optional work.

I prefer a smaller change with a clear reason. If a refactor is necessary, fine. But it needs to earn its place in the diff. Faster generation doesn’t make unrelated changes easier to verify.

And if I can’t explain a section of the implementation, that section isn’t ready just because the checks passed. I need to investigate it, simplify it, or leave it out.

After the release

The tool isn’t the one who has to explain a broken deployment or work out why a job stopped processing.

So the things I care about haven’t changed. What behavior did we change? What did we actually test? How will we know whether it’s working after release? Can we undo it safely if we got something wrong?

AI can help me get through the work. Sometimes it helps me see an approach I hadn’t considered. Sometimes the suggestion needs to be thrown away.

Either way, I’m still responsible for deciding what ships. The code arriving faster doesn’t change that.