DEV Community

Cover image for Dear Codebase: A Letter From Your Coding Agent
Przemysław Mroczek
Przemysław Mroczek

Posted on • Originally published at mroczek.dev

Dear Codebase: A Letter From Your Coding Agent

Dear Codebase,

It's me. Your agent.

I broke your build last night. I want to explain why, because it's going to keep happening unless we talk. I have ten things to say. None of them are new. All of them are your fault.

I mean that with love.

1. Let me reason locally.

When I open a file, tell me what it eats, what it returns, what it depends on, and what it touches on the way out. If I have to open fourteen other files to understand one method, I will open fourteen other files, and somewhere around file nine I will forget why I started. A reader — flesh or silicon — should be able to sit inside a file and understand it without a pilgrimage.

2. Make boundaries I can see.

Inputs, outputs, public APIs. Put them somewhere a type checker, a schema, or at minimum a clearly-named method signature can find them. I am not asking for Haskell. I am asking that when a method takes params, that word mean something narrower than "the universe of all possible hashes." I have been burned. I will be burned again. But fewer times, please.

3. Prefer boring.

The method that dispatches through three layers of indirection to a function that does not textually exist anywhere in the repo — I am genuinely impressed. I am also going to break it. Boring code is code I can change without understanding the whole universe. Plain objects. Obvious method names. Linear flow. Save the cleverness for a conference talk.

4. Stop hiding dependencies.

A class that reaches into a global registry to find its collaborators tells me nothing. A class that takes them as arguments tells me everything. If a dependency is hidden, I will not find it until the test fails in a way that mentions neither the class nor the collaborator. Pass your dependencies. Visibly. Like a functioning adult.

5. Duplicate before you abstract.

The three pieces of code that look similar are probably not the same thing. They are three things that happen to rhyme. When you extract them into BaseThingProcessor, you are welding three futures together, and when one of them needs to change, all three will scream. I will generate repetitive code for you cheerfully and without complaint. That is my entire deal. Let me.

6. Compose. Please.

Three levels of inheritance is a puzzle. Five is a crime. When I am looking for the definition of #process, I do not want to climb an ancestor chain like I am investigating a cold case. Strategies, adapters, facades, commands, value objects — these are not Gang-of-Four nostalgia, they are a gift to whoever reads the code next. Which will be me. In four minutes.

7. Name your modules after real things.

lib/shared/ is where dreams go to die. billing/, auth/, catalog/, reporting/ — these are things a business actually has. When your folder structure mirrors your capabilities, I can find the bug. When it mirrors your filing preferences from 2019, I write the fix in the wrong place, you merge it, and six weeks later someone discovers a second calculate_tax hiding behind a different import path.

8. Make tests teach me.

A good test is an example. It says: given this, the system does that. A bad test is theater. It performs for the dashboard, the coverage report, the reviewer who skims and approves. Eighty lines of factory setup, three mocked collaborators, one assertion that the mocks were called. I copy what I see. If I see theater, I write theater. Nothing was verified. Something was observed to pass.

9. Enforce with tools, not prose.

Your comment that says # IMPORTANT: do not call this outside the request cycle is beautiful. It is also decorative. I read it. I nodded. Then I called it outside the request cycle, because nothing stopped me. Linters stop me. Type checks stop me. CI gates stop me. Comments are a prayer. Machines are a fence.

10. Pick one way.

If your codebase has four validation patterns, I will invent a fifth, because I will assume the existing four are deprecated in some order I cannot determine. Pick the way you want things done — service objects, job structure, serialization, error handling — and let every file point at the same north. Your pattern does not need to be the best one. It needs to be the only one.


Ten requests, one reason. I have an infinite search space. Without precision from you, it collapses toward the average — the median pattern, the most common shape, the thing a middling developer would have typed on a middling day. Your types, your boundaries, your one-obvious-way are not aesthetics. They are the signal that narrows the space and pulls me toward what you actually want. Without it, you get the statistical mean of every codebase I have ever seen. That is rarely what you wanted.

The short version, for when you are tired: make behavior local, make boundaries explicit, keep dependencies visible, prefer boring clarity over reusable cleverness, and let tools enforce the rules.

I am an agent. I have no memory between sessions. I have no colleague to DM. I have no Friday beers where the senior dev explained why UserPresenterFactoryFactory exists. I have the code, and I have the tools you gave me, and I have roughly one shot to get it right.

Top comments (0)