A New Chapter
We’re moved into our new home. Time to frantically unpack so that it’s livable for the person dog/house sitting while we’re in Japan for RubyKaigi. This whole month is a bit of a whirlwind. To compound things, we’re likely launching a new online store on Monday.
I’m just glad to have an office again. Working in the living room did not work well for me. I’m really going to make it my own, since I spend so much time in there, but that’ll have to wait until at least this summer. In the meantime, it’s just nice to have a separate work space.

I had this week’s post all ready to go, but then because we had to pump the brakes on the site launch, I’ve had to push what I’d written to next week. Oops.
For a long, I’ve adhered to a design philosophy that focuses on pulling the “structure” of the application up to the highest level possible. In the context of Rails, that means that my controller actions and background jobs look a little more complex than some like.
This approach has a few benefits. Firstly, it makes it easier to see what a given action or job is doing at a glance. If you’ve worked on enough Rails apps, you’ve probably run into code that looked something like this.
class DoThingJob < ApplicationJob
def perform(id)
DoThing.call(id)
end
end
It’s an extreme case, but I’ve seen almost literally this plenty of times. Unless DoThing has some other call sites, it’s a useless abstraction. Anyone opening DoThingJob to see what it does find only that the princess is in another castle. It’s a useless abstraction. (Again, if this abstraction is because you need exactly this logic called from multiple places, that’s fine.)
Instead, I want to see domain objects getting orchestrated at this level. This “top” level (the level where the framework hands off execution to our code) arranges objects to achieve a given goal. If we design our objects and their interfaces well, it should be easy to understand what a given action/job does just by reading through this code.
This approach also gives us a natural place to inject dependencies. Dependency injection makes our objects more flexible, making them reusable in different contexts where different collaborators are needed. (It can also make them easier to test.)
Finally, it enables good1 tests. Our tests for the action/job can serve as integration tests. They don’t need to test every possible behaviour of the entrypoint, just that the components are arranged to achieve the rough desired result. Dependency injection and the natural parameterization of the underlying objects should make them easy to unit test, covering all necessary behaviours in tight, focused tests.
At some point, I should really write a more serious article on this, but I’m (sorry) using it explore a critique of LLMs. They don’t write code like this.
And they don’t write code like this despite it being easier for them to work with. The high-level overview found in my entrypoints avoids extra tool use, reading more files, and should help them gain context more easily. For many of the same reasons it’s good for humans, it’s also good for the code shoggoths. But they don’t write code like that.
If you’re asking an LLM to optimize anything (code, instructions, documentation, etc.) for an LLM, you’ve misunderstood the tool you’re using. They don’t “understand themselves” any more than you do, if not less. Maybe.
You can accomplish that with these tools by being very strategic in how your prompt them, and thinking carefully about what you actually want them to do. Or you could just spend that effort doing the task yourself. Up to you.
There was never a doubt what record I’d be recommending this week: Archspire’s Too Fast to Die, which dropped today.
Archspire is easy to explain: they are to death metal what Dragonforce is to power metal. Maximum speed. Undeniable musicianship. Beyond technical. And they have a new album. It will melt your face off.
What “good” means in this context is left as an exercise for the reader. ↩