I write code for a living and I write poetry, under a pen name, when nobody is paying. It is easy to keep the two apart. One is work, with tickets and pipelines. The other is the notebook. But more and more I notice I am doing the same thing in both places.
In a poem you write a line, read it aloud, and cut. You cut the adjective that explains what the noun already said. You cut the second image because the first one was enough. You move a word to the end of the line because that is where it lands. Most of the work is not writing. It is removing what does not need to be there until the idea stands on its own.
That is also the best description I know of refactoring.
Two crafts, one discipline
A poem has constraints: the line, the breath, sometimes the meter. Code has its own: the language, the types, the person who will read it in two years at three in the morning. Neither craft is free. Inside the constraint you choose. The choice is where the beauty is.
Poetry taught me to read my own code out loud, in my head at least. If a line needs a comment to explain what it does, the line is usually wrong, the same way a verse that needs a footnote usually is. If a method has three ideas in it, it reads like a sentence with three subordinate clauses. Technically correct. Tiring. Nobody remembers it.
Ugly and beautiful say the same thing
Here is one idea, said twice. Give me the names of the tests that failed.
Correct
r = []
for i in 0...res.length
if res[i][:status] != "passed" && res[i][:status] != "skipped"
r << res[i][:name]
end
end
Said once
failed = results.select(&:failed?).map(&:name)
Both work. Both pass the same test. The first one describes how the machine walks an array. The second one says what I wanted. You read it once and you have the idea. Nothing to hold in your head, nothing to decode.
The same goes for names, which are the closest thing code has to a word choice in a poem:
def chk(u) = u[:a] >= 18 && !u[:c].nil?
def can_sign_contract?(user) = user.adult? && user.country_known?
The second version is not longer because it is fancier. It is longer because the name carries the meaning, and the reader does not have to rebuild it. A good line of verse does the same. The right word replaces a sentence of explanation.
Beauty is not decoration
When engineers hear “beautiful code” they sometimes hear vanity: clever one-liners, a trick that makes you feel smart in review. That is not what I mean. Clever is often the opposite of beautiful. A clever line makes the reader work so the writer can look good. A beautiful line makes the reader’s work disappear.
A big part of my career has been test automation and APIs, and tests make this very concrete. A test is a small piece of writing whose only job is to be read when something breaks. If it reads like a sentence (this request, with this payload, should return this) the person on call understands the failure in seconds. If it reads like a puzzle, they rerun it and hope. Clarity in a test is not style. It is time someone gets back.
Beauty in code is respect for the next reader. That reader is often you.
What we lose when the words are free
I use AI coding assistants. I am not writing this to tell anyone to stop. They are good at the boring parts, and I am happy to give those away. The question I keep coming back to is a quieter one.
When a model writes the function, the expression of the idea is no longer mine. I describe what I want in a prompt, a block of code appears, the tests pass, and I move on. The code is usually fine. It is often the first version, the one with the loop and the index, because it is the most common shape on the internet and the model has no reason to cut. And I have no reason either, because I did not write it. I did not struggle with it, so I do not feel the extra words.
That struggle was the point. Looking for the shortest true way to say something is how you find out whether you understood it. In poetry, if I cannot find the line, it usually means I do not know yet what I want to say. In code it is the same. The awkward function is often a sign of an awkward idea. When the words come for free, we lose that signal. The code still runs. We just stop noticing that we never really thought it through.
There is also something smaller and more personal. Writing a good function used to feel like finishing a good line. A small click. I notice I get that feeling less often now, and I do not think it is because the code got worse. I think it is because I am typing less of it.
Keeping the pen
I do not want to go back to typing everything. I want to keep the part that mattered. A few habits help me:
- Treat generated code as a first draft, not a finished poem. Read it aloud. Cut what the idea does not need.
- Write the important parts by hand: the names, the domain model, the one function everyone will read. Let the assistant fill the rest.
- In review, ask “does this say what it means?” and not only “does it work?”. Both questions matter, and only one of them is checked by CI.
- Keep one place where you write slowly. For me that is the notebook. Maybe for you it is a side project with no deadline.
I have also been building two small experiments around this, mostly to play. Poem ↔ Code turns a short poem into a JavaScript function that keeps its shape, and turns code back into free verse. The meter of code reads a snippet the way you would scan a verse: line lengths, indentation, repetitions as rhyme, and a “meter” label at the end. Paste in something you wrote last week and see how it sounds. Then paste in something an assistant wrote for you and compare.
Code is still a language, and people still read it. As long as that is true, there is room in it for a well-chosen word, a line that says one thing, a structure that feels inevitable once you see it. I would like us not to give that up just because the machine can type faster than we can.
Poets have always had to choose every word. Engineers used to have that too. I would like to keep choosing some of mine.