A healthy dosage of pain

As always, I start with an apology. I made big promises two years ago to write a whole series of blog posts about something that had gotten me really excited at that point in time.

Either way, an unbelievable amount has changed since I wrote my last post. Being a SWE is now dramatically different from what it used to be. In that timeframe, we’ve gone from having LLMs assist with drafting docs and researching codebases to LLMs completely taking over triaging bugs, writing and reviewing code, to everyone at Google having their own personal assistant agent. I spend more time interacting with agents than I do with people (and by a wide margin). Having seen the merits and demerits of this incredible technology for long enough now, I feel good about sharing some opinions on where things seem to be headed at this point.

First, I want to clarify my stance on how I feel about the technology itself. LLMs are incredible at getting knowledge work done. I don’t think the technology is going away any time soon, nor should it. I often end up getting into debates with friends who feel less… warmly, should I say, towards the technology about how it takes away the joy in the process of making things. And they’re not wrong. There was a whole era of leaving behind a million print(“reached here 123”) all over the code to try and see what the hell was going on. And the satisfaction of eventually finding what was wrong is something that I think has been lost. But well, I also like being efficient with the tools I have access to and, well, it also helps that using agents to write code is the official policy for SWEs so…

¯\_(ツ)_/¯

Current LLMs are a lot better at writing code than I am anyways. So I let Gemini handle it for me. But that isn’t what I wanted to talk about. Let’s take a step back.

Pre-AI

Before LLMs got as good as they did, a significant part of my time at work was spent typing code. (I recently saw some code I hand authored maybe two or three years ago and shed a tear). Writing code was actually quite therapeutic. The best way I can describe it to a non-programmer is that it was like doing the dishes! You had already thought through what you were gonna do — the hard part was over. Now you just had to glove up and scrub scrub scrub. (I spent a long time deciding if I needed this em dash… Another thing LLMs ruined for the rest of us </3)

But it would also take me a long time to type things out by hand. I had to, as I typed away, also form mental models of what the codebase looked like. ClassUser goes as input into ClassBank where its AddToAccount(…) function gets invoked etc etc. Repeating this over and over each time I had to make a new code change to fix a bug or add a feature reinforced my understanding of what the codebase looked like. It was a painful process, but the pain had its upsides.

  1. A SWE knew exactly what the codebase looked like. Pre AI, you could tell me that there was a certain bug that made the system do something funny and I’d be able to give you a remarkably good guess on where to look for the bug.
  2. I could exercise judgement confidently on what were obvious no-gos because the code simply wasn’t built to support them.
  3. Most wonderfully, I’d be able to review a code change with ease. I’d have a pretty confident guess on if something was harmful or harmless.

The pain of authoring code by hand also came with a surprising benefit that I think no one had expected: it added a barrier on what was worth building and what wasn’t. We have a famous rule that a lot of us SWEs swear by: YAGNI (You Aren’t Gonna Need It). Building out something elaborately complicated was never someone’s first option, because who the hell wanted to type all that complicated stuff out and write tests to make sure it did what you thought it did? So people often shipped out the simplest (which is often the most maintainable) version of something.

Post-AI

Now here comes trouble. The barrier of building something just… vanishes. You can ship out the Super Duper Steroid Boosted version of the same thing. Cuz… why not! It takes Gemini the same amount of time to do it and it looks fancier so I can brag harder about it.

Now imagine every SWE you were working with having three or four beefed-up agents churning out the fanciest version of something that needs to be shipped out, day in… day out… That is what work has been all about for the last couple of months.

With the same number of reviewers as in the Pre-AI era, what this leaves us with is, ***drum rolls***

  1. A 3x increase in the number of code changes coming in for a review
  2. with a 3x increase in the complexity of each individual code change
  3. with a third of my comprehension of what the hell is even going on anymore

The human, has once again, become the bottleneck. And well, seems like the only way to combat a bad guy with an agent is a good guy with an agent. So that is how we end up with agents writing and reviewing code.

And I’m not trying to exonerate myself here. I am very much guilty of all of this. (And not to brag or anything, but I set up some of these “AI-DLC” practices for my team that have come back to haunt me). It’s fine to be getting a lot of this wrong. Our SWE world has changed dramatically and people are excited to do things in a different way. And I don’t think anyone really knows what the “right thing” to do is anymore. Addy Osmani, a member of the technical staff at Anthropic, writes a lot about Cognitive Surrender and Comprehension Debt, both of which seem to be widespread. (Pretty great reads! Would recommend). So even though I’m only like 70% sure of what the right solution here is, I’m 100% sure that no one has figured out a technical solution to this.

I had my own moment of “Wait a minute, what’s going on here” perhaps a month ago when I hopped onto a meeting with a team that was trying to onboard onto our service. They had a whole bunch of questions on how our system worked. We had answers for surface-level questions; “what happens if I do A and then B?“. Sure. That’s easy! But when questions got more specific “What happens if I do A and then B but something goes wrong and then I try to do E?“. Well…? Not too sure anymore.

My thoughts on the fix?

I’ve seen several really promising suggestions offered at various steps when human reviews have become the bottleneck. And spoiler: I don’t think any of them are going to work on their own; because I believe the problem is elsewhere.

Things I’ve seen suggested

Have rigorous testing systems

Well!? Have AI write a bunch of automated tests that ensure the system behaves in the right way. If you have extensive tests to cover every possible thing a user might do, your tests are gonna catch things before something goes wrong!

Sadly that never plays out. Build a fool-proof system and the world will produce a better fool. And often, I’m the fool. No bug has ever occurred from a scenario I thought to test for.

Have resilient systems

The trick is to build systems that are resilient. If we have great observability and telemetry to a point that we observe differences between the new version of the code and the old version of the code, we can rollback quickly!

Sorta follows the same folly here. We have an infinite number of ways two systems can differ from each other. You’re not going to be able to catch all the ways A and B diverge.

Spec-based engineering

Instead of having engineers focus on writing code, we have them focus on design. It’s like what architects do. They give the builders a blueprint specifying what the final structure ought to look like. Engineers author this blueprint, hand-review it, and then have agents build the rest.

Not gonna lie, this one is my favorite. I think there’s a lot of promise here. But I still don’t believe this will get us the same quality of software we shipped out Pre-AI era. Specs are meant to be broad. But we’re back to the same problem. If a spec gets too detailed, it’s no longer human readable. If it’s not detailed enough, it’s open to interpretation by an agent.

Exercise judgement on where to and not to involve humans

Engineers need to exercise their judgement on which code changes need human judgement and which changes do not. Let agents review and approve anything that doesn’t need human involvement.

The problem with this is that it’s sort of circular. A human reviewer needs context on the codebase to exercise judgement if an incoming code change needs human judgement or not. Remember how I described the way we used to build context by painfully hand authoring code changes? With that pain out of the picture, humans sadly do not have sufficient context to exercise any judgement. It’s a race to the bottom.

Now, to clarify, I’m not dismissive of any of the above solutions. I think more test coverage, better observability on your systems, building resilience, better process are all amazing. They will decrease the likelihood of introducing bugs or causing an outage. But it means very little to have these in a codebase you do not understand. There is little value in writing tests when you can’t be certain that the tests do what you want them to. There is little value in having observability when you aren’t certain what exactly is being observed. Having “more” of things only makes sense if you understand what you have “more” of.

The only thing that has worked for me?

Slow down. Bring back some pain.

As unfortunate and tragic as that might sound, the only thing that has served me well is to be more deliberate with things. I still ask my agent to assist me with reviewing things. But I also read docs and code line by line. If something looks too complicated for me, I ask my agent, “Dude… is it just me or does it look hella complicated to you too? Is there a simpler way to do the same thing?” If something is still unclear to me after reading the code myself, I ask my agent, “Bro… what the hell is going on here?”. It takes me more time. It’s more painful. But this back-and-forth in my opinion is necessary. It helps ingrain things on what the codebase does. It helps me make sure that whatever gets checked in is as close to what I’d have expected if I wrote the code myself. And most importantly, it builds that much needed context for me to confidently exercise judgement (which after all is basically what the SWE role has become).

Guardrails and comprehension are two very different things. In the agentic world, guardrails are often dressed up as alternatives to comprehension. It was never an either-or. I think it’s going to be a skill in itself to distinguish where you lack the context to make effective judgements.

And again, don’t beat yourself up about having to intentionally slow yourself down. It’s just how our brains work (I’m no doctor). Until the day comes when agents purchase software written by other agents, a human will be needed at some point in the loop. And for a human to really be in the loop, there’s some pain that’s sadly necessary for them to really understand what the software does.

Leave a Reply

Your email address will not be published. Required fields are marked *