Code is still a side effect

“Software development is a learning process, working code is a side effect.”

I wrote this line a long time ago, to capture the essence of software development in an alien business domain: coding becomes obvious once we understand enough of the problem domain to define clear, actionable models. Coding is easy once you know what to write.

I like this sentence because it exposes our weakness as software practitioners. If learning is the bottleneck, the core question for our professional impact changes: “How good am I in learning?” Coding is only a fraction of our job. Without proper domain understanding, we may spend an entire career blaming our business experts and users for their lack of clarity and precision, while writing clean code that doesn’t solve business problems.

How did we learn how to learn?

In my university years, learning has always been more of an afterthought than a discipline.

Students followed lessons, using coffee as an antidote to boredom, and played experiments with information retention, repeating patterns from school: some took notes, some spent evenings rewriting them, distilling the essence of the lessons. Most students were writing notes, but a few were typing; some were taping the lessons, too.

Passing a written exam required practice. The more the better: students didn’t want to be caught off-guard by a new type of exercise, and the speed of execution often mattered too. Repeated practice and a broad selection of challenges delivered confidence, fluency, and speed.

Passing an oral exam (a house specialty of Italian universities) required a different set of skills: studying mattered more. Some students were re-reading their notes for the nth time. Others were highlighting and commenting on key information in books and notes. Reading loud or quietly made a difference, and many used to rehearse the topic like lawyers preparing for a court debate.

Some topics had challenging home assignments; others were so interesting that they sparked side projects: I remember spending long nights playing with neural networks in C++ or trying to build a universal translator. I remember feeling guilty about my digressions, but I later realized I was learning much more when I had time to follow my curiosity down into a rabbit hole.

There was no official discipline, only experiments. The students who managed to graduate received feedback from the system: You’re good at passing exams. Here’s your degree.

It took me a few more years to become serious about understanding the dynamics of learning. I knew I was a quick learner, but I didn’t know why. After getting into teaching practice, reading a few books, and experiencing different learning formats, I came to understand what worked better and what was close to superstition. Summarizing and recalling reinforced retention, while repetition generated different pathways: Have you tried doing math in a foreign language? It’s a safe, frustrating exercise that shows how your brain is using language to take shortcuts. Seiperottoquarantotto is probably a key to a hashmap in my brain.

But I also realized how the practice of business software development was affected by this lack of a learning theory. Development teams were segregated, with limited possibilities of interaction with users and experts. Many workplaces were, in fact, optimized for looking busy; coding is what we are paid for, so …let’s code! Who cares if we are coding the same feature over and over again!

Learning domain complexity

Learning domain complexity in a professional setting turned out to be a lot different from learning as a student. There were no lessons or books, just “special moments” when someone explained something to us, or we captured a meaningful detail in a conversation. Most learning happened in “detective mode”: you talk with people, listen carefully, collect evidence, and form a theory of what people need and how a solution might actually work.

But business information is fragmented and often incomplete. The sources may disagree or use different languages. Last but not least, a business domain is a moving target: competitive markets require a continuous effort to rethink processes and rules, so what you learned yesterday might not be true tomorrow.

Domain Expertise is not found in books.

Misunderstandings are part of the game, and the combination of quick feedback loops with the ability to rewrite without nasty side effects was a good way to navigate this complexity. The perfect truth upfront didn’t exist, or we weren’t ready to grasp it without a couple of mistakes.

If we have the luxury of solid architectural foundations and coding practices, the core principle still holds: with a team of skilled software professionals, understanding what to write yields greater impact than simply writing the code. Good domain understanding, captured in clean models, makes the coding simple.

Domain-Driven Design was an incredibly sharp toolkit in navigating this complexity. Bounded Contexts  provided a solid foundation to build models that won’t collapse under the weight of contradictions in the business narrative. The way experts described their business was rarely precise enough to be a software specification.

What changes with AI?

Modern LLMs are incredibly versatile tools that can support software development operations in many ways, from coding to browsing and producing documentation, just to name a few. But the application space is wider: some folks seemed excited by the possibility of extracting documentation from EventStorming sessions, enriching and validating content, and literally whatever comes to mind.

AI-assisted coding requires coherence

Let’s have a look at the coding part first. This is where Claude, Codex, and Copilot shine. I am not diving into the different styles of interactions - Vibe Coding, harness engineering, and so on - but mostly on a few traits that tend to affect any type of interaction, and may require awareness, mitigation, and/or corrective action.

  • Trust: by default, AI trusts the information source (us). We can inject different interactions, like the popular /grill-me, to enrich the provided information, but AI wouldn’t challenge us, by asking our colleague instead.
  • Text is the default media. Be it on a chat or in markdown files, text is the dominant format. We tend to assume it’s fine, but in terms of learning, the information loss is significant. We can describe things in text, but text makes a pretty lousy medium for learning. In a lesson, teachers can emphasize important information through voice, pauses, and gestures. They can add drawings or diagrams and respond to the classroom mood. Learning what requires an active practice is a whole different matter: would you learn to play sports or music just by reading text? Text is a practical medium for writing, but not the ideal choice for learning.
  • Flat dictionary. LLMs tend to assume a flat dictionary, where each term is mapped to a single meaning within the scope of the conversation. They are smart enough to detect contextual information for common polysemic words, but they usually fail to detect subtle differences in business domains. When it comes to code, this becomes a highway into coupling and an accelerated big ball of mud.
  • Single source. There is an implicit notion of everything together. Despite having multiple sources (prompt, code, harness, documentation), not everything is equally good. Code written by juniors in their early days isn’t exactly the most reliable source of truth. Sometimes we might need to explicitly add contextual information to navigate incoherence. “`Document XYZ.md` reflected the original vision, before moving to implementation. The actual implementation may have gathered more feedback from the field”
An uneducated AI is a continuous pull towards coupling
You won’t notice this in small pet projects, but this will backfire spectacularly if you’re working in business domains, trying to keep coupling under control in your modular monolith. The dominant pattern in your AI training set is data-centric.

Can we balance this?

A strategy that’s working for me is to make Bounded Contexts a first-class citizen of the AI harness. A Bounded Context provides a local language with no ambiguity and the boundary of applicability. We just need a way to “align our bounded contexts with the context window”.

For local features, this means relying on a smaller, unambiguous glossary. Less rework, fewer tokens, everybody happy.

One way to achieve this may be to provide layered documentation: a high-level document describing the overall purpose of the application (a modular monolith in my scenario) and a more detailed description of each model’s purpose and language.

The fragmentation helps balance the language precision with token consumption. There are usually a few more files to describe external integrations, architectural patterns, and so on.

A simplified tree of a Bounded Context-aware harness

This documentation infrastructure seems a decent way to turn your AI assistant into a business-aware contributor, but it doesn’t come for free. What used to be that little page of documentation no one cared to read now becomes the foundation of your harness, and it has to be explicit and coherent.

In fact, coherence seems to be a very interesting property of a codebase when working with AI.

Coherence: How much the different artifacts support the same vision.

An incoherent codebase can be a token burner and a rabbit hole generator, sinking time and money at accelerated speed.

Can LLMs help in building this coherence?

A little, but not as much as marketing and hype would have you believe. Providing coherence is on you. Turning tacit knowledge and unresolved ambiguities into a coherent documentation will take time and an unexpected number of decisions - there’s a better wording for this.

Interestingly, Ubiquitous Language seems like the perfect tool to enforce this coherence. But Ubiquitous Language is more the continuous tension towards language precision, than the artifact itself.

AI can provide some help here, maybe by suggesting terms when you’re stalling. But you can’t offload this pain. While technically you can extract a dictionary from pictures of an EventStorming session, it doesn't sound like a great idea.

Building coherence is the moment where the deepest learning happens, and it has to happen in your brain. AI can build brilliant, plausible shortcuts between digital artifacts, but it takes focus to rewind what happened in a collaborative modeling session and detect narrative incoherence or potential conflicts among stakeholders.

What AI can do very well instead is detecting incoherence. Scanning documentation and the codebase, an LLM can produce maps and reports about the places where your codebase and the documentation mostly diverge.

Balancing learning and coding

Coherence enables LLMs to work more effectively with business software. But coherence is still a byproduct of human learning, which aligns more with neuroscience than with AI hype.

My current stance is protect the learning, build explicit coherence, enjoy AI-assisted coding, in a Domain-aware harness.

Coding is in the middle of a paradigm shift, but learning is still a brain thing.


Context - any AI-related post is meaningless without it

  • Working mostly on business code. 15-20 interconnected Bounded Contexts
  • Greenfield and brownfield scenarios: migrating a legacy application to a different language (porting, rewriting, and redesigning) and evolving the legacy application.
  • Mostly working on a modular monolith, mostly CQRS/ES with good test coverage
  • I master the domain. I wear many hats. This simplifies a lot of things.
  • I have 20+ years of Domain-Driven Design. I am obsessed with language precision.

Photo by John Bell on Unsplash