DEV Community

Cover image for I Let AI Type the Code and Lost the Map
Boopathi
Boopathi

Posted on Edited on

I Let AI Type the Code and Lost the Map

I coded by hand for about three years. When a feature was done, I could close the file and still remember it. I knew which function ran first, which query could return too much, and where a bad input would break.

That was normal for me in those three years. For the last year and a half, AI has written most of the code I ship. I still review the diff. A week later I don't remember how the feature runs, because I didn't type it.

The years of typing

Hand coding took longer, but when we finished we actually knew the code. We read the function, then the one it called, then the schema under it. By the time the feature worked, we had a mental map of the flow. The request comes in, the body gets checked, a record loads, something is written, a side effect runs, and the response goes out.

A small handler is enough to show what that feels like.

async function createOrder(req, res) {
  const input = validateOrder(req.body)
  const customer = await loadCustomer(input.customerId)
  const order = await saveOrder(customer, input)
  await notifyWarehouse(order)
  res.json(order)
}
Enter fullscreen mode Exit fullscreen mode

A test fails. The warehouse never hears about the order. We don't start at the top of the repo and wander around. validateOrder and loadCustomer already ran, because the test got as far as a saved order. The break is in notifyWarehouse, or in the shape saveOrder passed to it. We open that one function. Most of the time the bug is right there, and the fix is small.

If we still can't see it, we do the usual thing. Open the debugger, or add a log on the value we thought we understood. We already have a guess. We just check it.

A production issue works the same way. A user is stuck, and we still try to walk the flow in our head first. Which function, which query, which empty case. If we can't see it, we run the code locally and debug it there.

For me, after those three years, the map was just bigger. New code sat on top of code I already understood. A bug in a file from six months ago was still something I could trace, because I had typed it and I still remembered the order.

After vibe coding

Then AI started writing the code. For the last year and a half, that has been my normal day. We describe the feature, AI writes it, we read the diff, and we ship. We do look at the lines. A week later we don't remember them, because we never typed them. Looking at a function once is not the same as writing it. Later a test fails, or a user hits a case we didn't write, and we have to open the file and start over.

So we read the code again from the top, as if someone else wrote it. Or we paste the error and ask AI to fix it. A few lines change. Something else breaks. We paste that error too. The chat gets longer, the diff gets messier, and we still can't tell a teammate which function owns the bug.

We feel busy because the code keeps changing, but we still don't have the flow in our head.

Production is worse. The alert comes in and we don't know where to look. We know what the feature does for the user. We don't know the code well enough to say which branch, which query, or which missing field. So we ask AI again. If the only memory of the feature is in a chat, we have no idea when production breaks.

From there the fix is brute force. We ask AI. We ask again. We are not really thinking it through. AI adds a null check, then a retry, then a filter that is not even the real bug. The error goes away. We still don't know why it happened, so the next bug starts from the same blank place.

The fundamentals go missing in the same period. Before AI, if we didn't know something, we went and read. How the database picks an index, how the language passes that object, whatever sits under the feature. Then we used it, and we checked it, because we were the ones who had to deal with it when it broke.

Now AI does that part. It writes the query. It picks the API. It can explain the idea in the same chat, and once we close the tab the explanation is gone. We didn't learn it, and we barely checked it.

await invoices.find({
  accountId,
  createdAt: { $gte: from, $lte: to },
})
Enter fullscreen mode Exit fullscreen mode

We used to stop on a query like this. Is there an index on accountId and createdAt, or only on accountId? What happens when from is missing and the range is open? What ends up in memory if the filter matches way more than we meant? We looked that up, then we wrote the call. With vibe coding the query shows up already written. It works for the normal case. We never really sit with the database, the language, or the idea under the feature.

I don't want the old way back, where every line is typed by hand. Work moves fast. If we are slow, the team feels it, and the company will not accept it. Everyone is looking for the speed.

The skeleton, then the model

What works for me is to keep the part I need in my head, and let AI write the rest.

On a big feature, I start with the flow that is already there. I read it. If it is messy, I ask AI how it works, and I stay with that until I can explain the flow myself. After that I write code.

I write pseudo code, not the full thing. Function names, and what each function should do. Empty bodies are fine. Those decisions have to be mine. If AI decides them, I will forget this feature the same way as before.

Say the feature is an invoice export. The first draft is only this.

async function exportInvoices(accountId, from, to) {
  const account = await loadAccount(accountId) // 404 if missing
  assertCanExport(account) // 403 if this caller cannot
  const rows = await loadInvoicesInRange(accountId, from, to) // never the whole collection
  const file = await writeCsv(rows) // no rows still produce a file
  return uploadAndLink(file)
}
Enter fullscreen mode Exit fullscreen mode

This is small enough to remember. The important rule is already there, the one about not loading the whole collection, and it was written before any real implementation.

Then I ask AI to turn that into code. I don't ask it to come up with the feature. A prompt like "build an invoice export" puts me back in the loop. A prompt like "fill these functions, and keep the comments true" gives me something I can still see.

async function loadInvoicesInRange(accountId, from, to) {
  return invoices.find({
    accountId,
    createdAt: { $gte: from, $lte: to },
  })
}

async function writeCsv(rows) {
  if (!rows.length) return ""
  const header = Object.keys(rows[0]).join(",")
  const body = rows.map((row) => Object.values(row).join(",")).join("\n")
  return `${header}\n${body}`
}
Enter fullscreen mode Exit fullscreen mode

When I review, I put the comments next to the code AI wrote. The account check is in the caller. The permission check is in the caller. Empty rows still return a file. The open range is where it missed. loadInvoicesInRange trusts from and to. If either one comes in empty, the filter matches too much and the whole collection can come back. That rule was in the skeleton, and the implementation left it out.

We catch that because we typed the rule. If AI had written the function from one line, we would paste the timeout back into the chat, it would add a limit somewhere, and we still would not know why it timed out.

We keep the mental map if we type the decisions and let AI type the rest.

The next time this feature breaks, we start from that skeleton. An export times out, and we already suspect the function that loads the rows, because we wrote that warning before the code existed. We open it. If the guess is wrong, we use the debugger. It is the same habit as the years of typing, on a feature we did not have to type end to end.

This is the part I don't want to give up. AI can write the function bodies. I still want to know what a function is for, and I still want to be able to debug it from my head.

Where this goes

Some people will say we will stop caring, the same way we stopped caring about assembly. There was a time we wanted to know how it worked. Over time we didn't. I think AI reaches that same place. The model writes the code, and most of us will only care about what we asked it to do. I agree with that.

Until then, knowing how the code works helps us stand out. Shipping fast gets the feature out. When production breaks, someone still has to say where it failed without opening a chat. That is the practical reason.

The other reason is just mine. Learning is a joy. We read, the concept clicks, and then we can use it ourselves. Vibe coding gives us the result and skips that part. I don't want to skip it.

If this last year has felt like the same loop, I want to hear how it has been going, and what is working.

Top comments (1)

Collapse
 
robertpaschal profile image
Odinaka Robert Nnamani •

Skeleton first, model fills the rest, keeps the map yours.