<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Code for Humans and Machines]]></title><description><![CDATA[Designing AI-aware software in practice through practical refactoring patterns and principles.]]></description><link>https://adamtornhill.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!BnIF!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ceb343-309b-4226-b2b2-387db9b14e27_608x608.png</url><title>Code for Humans and Machines</title><link>https://adamtornhill.substack.com</link></image><generator>Substack</generator><lastBuildDate>Wed, 02 Sep 2026 13:45:47 GMT</lastBuildDate><atom:link href="/__u/adamtornhill.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Adam Tornhill]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[adamtornhill@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[adamtornhill@substack.com]]></itunes:email><itunes:name><![CDATA[Adam Tornhill]]></itunes:name></itunes:owner><itunes:author><![CDATA[Adam Tornhill]]></itunes:author><googleplay:owner><![CDATA[adamtornhill@substack.com]]></googleplay:owner><googleplay:email><![CDATA[adamtornhill@substack.com]]></googleplay:email><googleplay:author><![CDATA[Adam Tornhill]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Beyond Lambdas: Raising the Abstraction Level of Functional Code]]></title><description><![CDATA[Good software design raises the abstraction level until the code communicates the domain rather than the mechanics.]]></description><link>https://adamtornhill.substack.com/p/beyond-lambdas-raising-the-abstraction</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/beyond-lambdas-raising-the-abstraction</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 01 Sep 2026 05:21:33 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a109ff23-de9e-4399-aaed-64275b699623_1729x910.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Anonymous functions &#8212; aka lambda functions &#8212; have made their way into virtually all mainstream languages by now. These languages have been adopting functional programming techniques for the past decades (e.g. LINQ in C#, Streams in Java), and lambda functions are an inherent part of that paradigm.</p><p>The nice thing about lambdas is that they optimize for writing code. When coding, it&#8217;s quite convenient to stay within a function&#8217;s boundaries and just flesh out detailed steps as lambdas that are then being passed to map/filter/reduce-like operations.</p><p>The <em>bad</em> thing about lambdas is that they optimize for writing code. They do so at the expense of reading code, which is arguably a much more frequent activity.</p><p>So let&#8217;s start from a specific lambda example and then refactor our way towards better abstractions.</p><h2><strong>Name the nameless</strong></h2><p>Quick, what is the following code doing?</p><pre><code><code>rolls.stream()
    .map(roll -&gt; roll + 2)
    .filter(roll -&gt; roll &gt;= 15)
    .sum();</code></code></pre><p>It&#8217;s a mere 3 lines of code, and the mechanics of each one is trivial. Yet the purpose, intent, and business rules remain opaque. It could be anything.</p><p>The lack of explicit domain concepts is a warning sign. Instead, we should raise the abstraction level by naming those lambdas to reveal the code&#8217;s intent:</p><pre><code><code>rolls.stream()
    .map(AttackRoll::applyStrengthModifier)
    .filter(AttackRoll::isSuccessfulHit)
    .sum();</code></code></pre><p>The preceding code is no longer a stream of seemingly random operations. Instead it communicates the concepts and rules from the domain. (In this case: Dungeons &amp; Dragons).</p><p>The new methods &#8212; <code>applyStrengthModifier</code> and <code>isSuccessfulHit</code> &#8212; are trivial one-liners. You implement them according to the idioms and features in your programming language of choice. For Java, I&#8217;d go with either private static methods or, if I notice groups of related abstractions, a private class. In Python or Clojure, we get away with even less syntactic noise: just use private module-level functions.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2><strong>Balance the trade-offs</strong></h2><p>A fair objection at this point is that the proposed approach leads to more lines of code. That&#8217;s true, but not necessarily a problem:</p><ul><li><p>Abstractions can be simple. Ridiculously simple.</p></li><li><p>Abstractions don&#8217;t have to be re-used to motivate their existence.</p></li><li><p>Lines of code are not a finite resource. Some abstractions might lead to more code. That&#8217;s a trade-off.</p></li></ul><p>My short test for any abstraction is: if it elevates the level of the code, then it has earned its rights.</p><p>Granted, the recommendation to avoid lambdas didn&#8217;t come easily. I&#8217;m a Lisp hacker by birth. (No, not really, but I always wanted to write that sentence.) But I did spend the past 20 years doing functional programming, and have watched my style gradually migrate away from lambdas.</p><p>Naming functions &#8212; even those trivial one-liners &#8212; makes a large difference when returning to code you wrote months or years earlier.</p><h2><strong>Abstract the pipeline steps</strong></h2><p>I usually take this abstraction a step further when the programming language allows it. Java doesn&#8217;t fall naturally into that category, so let me switch to a simple Clojure example.</p><p>Here&#8217;s the same problem in Clojure:</p><pre><code><code>(-&gt;&gt; rolls
     (map (partial + 2))
     (filter #(&gt;= % 15))
     (reduce +))</code></code></pre><p>If you haven&#8217;t seen Clojure before, then the threading macro <code>-&gt;&gt;</code> probably looks odd. It&#8217;s just a convenient way of passing the result of one function as the input to the next.</p><p>And just like in the Java example, refactoring away the lambdas clarifies the intent:</p><pre><code><code>(-&gt;&gt; rolls
     (map apply-strength-modifier)
     (filter successful-hit?)
     (reduce +))</code></code></pre><p>But we can take it one step further, and also name the pipeline elements. This makes all the difference:</p><pre><code><code>(-&gt;&gt; rolls
     with-strength-modifier
     successful-hits
     -&gt;damage)</code></code></pre><p>With that, the code now reads like a story, telling the rules of the domain. We&#8217;ve come a long way with simple steps. Anonymous functions are anonymous thoughts.</p><h2><strong>Optimize for reconstruction work</strong></h2><p>By naming lambdas, we raise the abstraction level and add information that wasn&#8217;t previously visible. This is important since much code needs to be prepared to evolve.</p><p>When picking up a new coding task, that work often happens within the context of an existing codebase. This is where proper abstractions pay off by limiting the necessary reconstruction work. And the closer our abstractions reflect the problem domain, the easier that will be.</p><p>That&#8217;s also true for coding agents as captured in the <a href="/__u/adamtornhill.substack.com/p/clear-software-design-principles">CLEAR principles for AI-first codebases</a>. We will all do a better job when the code&#8217;s purpose is expressed structurally. Small abstractions add up, so make them a habit.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Code for Humans and Machines is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Controlling the Uncertainty Machine: Do You Still Need to Read AI Code?]]></title><description><![CDATA[Human attention should follow uncertainty. We don't need to read all AI-generated code. But we need to make the code we do read count.]]></description><link>https://adamtornhill.substack.com/p/controlling-the-uncertainty-machine</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/controlling-the-uncertainty-machine</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Thu, 20 Aug 2026 05:23:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!d66r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With AI coding, <a href="/__u/adamtornhill.substack.com/p/compressed-cognition-the-hidden-cost">human attention and focus become bottlenecks</a>. AI-first codebases have a different context and audience. As a reaction to that, we need to reconsider traditional &#8220;best practices&#8221; to reap the full benefits of coding agents.</p><p>As developers, we&#8217;re used to being accountable for all code we write. Coding agents do <strong>not</strong> change that. But we need to use our time effectively, and I simply found that trying to grasp all code in detail is no longer efficient; if we approach agentic coding the way we tackle manual coding, we&#8217;ll spend the majority of our time deconstructing meaning from code. It&#8217;s wasteful and cognitively effortful.</p><p>That obviously doesn&#8217;t mean that I no longer care about readability, code structure, or architecture. Quite the contrary. This whole blog is about maintainable code. Rather, I leverage automation and strict processes to relieve me of the code-reading burden without incurring unacceptable risks.</p><h2><strong>Human inspection is driven by uncertainty</strong></h2><p>A question like &#8221;do we still need to read all AI generated code&#8221; is pointless in isolation. The answer depends on the task, and more specifically on the <em>uncertainty</em> inherent in each type of task. That is, how much of the intended solution&#8217;s behavior and structure is already understood and represented in the existing system.</p><p>That task uncertainty drives both the relative autonomy I grant a coding agent, and the effort I spend reviewing the resulting code.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!d66r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!d66r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1009918,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/211554432?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!d66r!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa404b65-2f6e-41d4-a057-9bb87628337f_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Uncertainty determines how much code I inspect. The more exploratory the task, the deeper I go.</figcaption></figure></div><div class="paywall-jump" data-component-name="PaywallToDOM"></div><p>Consider a bug fix. Here it&#8217;s usually enough to inspect the evidence for the fix. Reproduce the problem, fix it, and have the new tests pass. Since the majority of bugs are local and contextual, bug fixes rarely require novel designs or architectural changes. They are naturally constrained.</p><p>Contrast those bug-fixing tasks with the first iteration on a new feature or capability. The existing structure and patterns in a codebase are important context that guides coding agents. When doing novel work, establishing those structures is key. This requires more detailed involvement. However, it doesn&#8217;t require that I read <em>all</em> code. Let&#8217;s explore the process.</p><h2><strong>Tests as human/agent abstraction boundaries</strong></h2><p>Understanding code we didn&#8217;t write ourselves is one of the hardest parts of software engineering. If we approach agentic coding the way we tackle manual coding, we&#8217;ll spend the majority of our time trying to deconstruct meaning from code. We&#8217;d effectively turn ourselves into legacy code maintainers. That&#8217;s a mentally draining place to be and is unlikely to speed up anything.</p><p>So instead I&#8217;ve come to accept that I&#8217;ll no longer know every line of code. I never read all AI-generated code. But, and this is important, make the code you <em>do</em> read count.</p><p>The pattern that worked for me is to focus my manual review efforts on tests. I typically instruct my agent &#8212; after the planning stage &#8212; to generate the end-to-end (e2e) tests first. I then review, and iterate on those tests together with the agent.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><p>A strong test suite serves as a boundary between the code I do inspect and the code I give the AI autonomy to develop.</p><p>Starting with e2e tests solves the validation problem: how do I ensure that the AI generates the <em>right</em> code?</p><p>With the e2e tests nailed down, I typically let the agent proceed and write the code that makes them pass. I rarely spend much time looking at the actual implementation.</p><p>The possible exception is novel features that don&#8217;t yet have an architectural home. Software development never was, and still isn&#8217;t, a streamlined, simplistic transformation. In such cases, I do perform spot inspections of the code. But again, I don&#8217;t focus on details, but rather on the overall structure and patterns to lay a strong foundation for the next iterations that can be more autonomous.</p><h2><strong>Turn review findings into future guards</strong></h2><p>Now, before we proceed to discuss code quality, let me emphasize that I do spend <em>a lot of time</em> iterating on those tests. AI-generated application code is rarely optimal out of the box. But it&#8217;s usually decent. The test code? Not so much. So it pays off to run those extra test refactorings.</p><p>My focus for the e2e test design is to optimize for ease of inspection.</p><p>The iterations and refactorings I do are typically:</p><ul><li><p>Bringing the tests and code closer to the domain.</p></li><li><p>Looking for gaps and omissions, including negative tests and error reporting.</p></li><li><p>Introducing additional abstractions to document the intent.</p></li></ul><p>That is, context that helps future iterations.</p><p>I do resist the temptation to change the code myself. Instead, I instruct my agent to change it and, once I&#8217;m satisfied, I instruct the agent to capture the transformation as a forward-looking SKILL. That way, my review findings turn into future guards and guidance.</p><h3><strong>Enforce what you don&#8217;t inspect</strong></h3><p>So, how can I guarantee that the uninspected application code remains maintainable and easy to change?</p><p>You occasionally hear that code is all about outcomes: what business value does a piece of code enable? That&#8217;s partly true, but also a <s>massive</s> bit of an over-simplification.</p><p>Code isn&#8217;t just produced and then lying dormant as an immutable artifact. Successful features attract change: improvements, extensions, and additional capabilities. Typically the most business-critical code experiences the largest volume of changes. Making those changes easy is what good software design is all about.</p><p>In sharp contrast to early hype, AI didn&#8217;t make software maintenance go away. Rather, AI amplified the need for maintainable code. As <a href="https://arxiv.org/pdf/2601.02200">our research discovered</a>, a coding agent is even more picky about code quality than we humans. The bar is higher. So how do I ensure that my code is maintainable if I don&#8217;t review it? The solution is a multi-layered safety net.</p><p>Over the past six months, I&#8217;ve built up a collection of SKILLs that capture my preferred coding style, design principles, and application-specific rules for architecture, etc. Those SKILLs grew gradually as feedback to flaws in AI-produced code.</p><p>Still, code quality cannot be left to chance, hoping that Claude or Codex are having a good day. Consequently, I use deterministic tools as part of my workflow. Some of these tools are commercial (e.g. vulnerability scanners, and the <a href="https://codescene.com/product/code-health-mcp">CodeHealth MCP</a>), others like linters are free. I then complement those tools with custom-built domain-specific checks to enforce architectural rules and even to catch some of the most common e2e sins.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!F4gC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 424w, /__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 848w, /__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 1272w, /__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!F4gC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png" width="728" height="839.568493150685" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1347,&quot;width&quot;:1168,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:1048628,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/211554432?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 424w, /__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 848w, /__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 1272w, /__u/substackcdn.com/image/fetch/$s_!F4gC!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14faa472-bac7-407f-8445-5b9b1d041908_1168x1347.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The general human/agent abstraction boundary. In practice, the boundary moves with the uncertainty of the specific task.</figcaption></figure></div><p>The point is that these rules and constraints need to be enforced. Deterministically.</p><h2><strong>The hardest thing to refactor is our habits</strong></h2><p>Given that I had typed out code by hand for almost 40 years, becoming comfortable with <em>not</em> reading code was a large mental shift when going agentic.</p><p>Almost a year into my agentic journey, I&#8217;m now quite confident that I don&#8217;t need to know every line of code. I get that confidence by knowing that the system behaves as intended, remains maintainable, and lives within the established boundaries.</p><p>I also made peace with the fact that agentic code might not look <em>exactly</em> the way I would have written it myself. Familiarity is a poor argument for resisting change.</p>]]></content:encoded></item><item><title><![CDATA[Five Programming Books That Changed How I Think]]></title><description><![CDATA[A look beyond the usual top five to discover other books that are profoundly novel. Each book influenced how I think about code.]]></description><link>https://adamtornhill.substack.com/p/five-programming-books-that-changed</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/five-programming-books-that-changed</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 11 Aug 2026 14:30:46 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/004b2f9a-754b-4e8c-8a59-39901fb742e0_1588x990.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Since you&#8217;re reading this post, I think it&#8217;s safe to assume that you &#8212; just like me &#8212; love top five lists. I click on more lists than I care to remember, but often end up doing just a casual scroll. Most lists tend to rehash the same five classics.</p><p>In this article, I&#8217;d like to go beyond the usual suspects and highlight books that influenced how I think about code.</p><p>This selection is from books written pre-2015. Why stop there? Simply because a decade is the time I need to truly see the impact a specific book had on me.</p><p>The litmus test is: did the book a) change my coding style, and b) was it a sustainable change that made a positive difference?</p><h2><strong>My take on the classics</strong></h2><p>It wouldn&#8217;t be fair to just skim past the common recommendations. So let me fall into my own trap with a brief take on those.</p><p>First, <em>Refactoring</em>, <em>Domain-Driven Design</em>, and the <em>Pragmatic Programmer</em> are all classics for a reason. Amazing books, and mandatory reads.</p><p><em>Working Effectively with Legacy Code</em> deserves all its praise, and of course everyone (yes, really) should check out the <em>Mythical Man-Month</em>.</p><p><em>Clean Code</em> is another standard recommendation, but that one wasn't for me. There are useful design principles in there worth studying, but the book as a whole is too narrow and a bit too dogmatic for my taste. Your mileage might vary. I'd point to <em>Modern Software Engineering</em> by David Farley instead. (See &#8212; There are exceptions to all rules. Even my "pre-2015" rule.)</p><p>With that said, it&#8217;s time for my own list. What do we find when we look beyond these classics?</p><h2><strong>Beyond the usual suspects: Adam&#8217;s Top 5 Programming Books</strong></h2><ul><li><p><em>Smalltalk Best Practice Patterns</em>. True, Kent Beck is better known for his later work, which is excellent too. But <em>Smalltalk Best Practice Patterns</em> is particularly strong on coding style. I learned a lot by just reading the code examples. Small tweaks to names and abstractions add up. No one captures that better than Kent.</p></li><li><p><em>Structure and Interpretation of Computer Programs</em> (SICP) by Gerald Jay Sussman and Hal Abelson. SICP is my all-time favourite programming book. It&#8217;s also where I learned about the power of wishful thinking when coding. SICP uses wishful thinking as a design tool: write code as if the abstraction already existed. Pretend. Then, once the ideal abstraction has taken shape, you go ahead and implement those functions. This meta-level is what made SICP so valuable. Ultimately, it&#8217;s a book that teaches how to think about code and problem solving. I&#8217;m tempted to even say that SICP is more a work of art than a pure coding book, but I won&#8217;t go there. A beautiful book.</p></li><li><p><em>Paradigms of Artificial Intelligence Programming</em> by Peter Norvig. Learning the AI described in this book probably won&#8217;t land you a job today. But reading the code examples will transform how you think about source code. The book shines when it comes to code comments, a topic that I&#8217;ve never seen demonstrated well in other sources. Here we get to see how comments become valuable as a narrative that explains both intent and reasoning. Brilliant, just brilliant.</p></li><li><p><em>Facts and Fallacies of Software Engineering</em> by Robert L. Glass. In essence, this is a book about an industry that refuses to learn. That was true 25 years ago when this book was published, and it&#8217;s probably twice as true today. (Just think about all the AI adoption metrics being rolled out &#8212; back to productivity mistaken for lines of code produced, only more elaborate. And expensive). What I like about this book is that Glass doesn&#8217;t present anything new. Quite the opposite, actually. Rather, it&#8217;s about research lessons that we all should know, but tend to forget. Ever had to do an estimate, or plan according to a requirements spec? Or maybe you thought that enough eyeballs make all bugs shallow? Then this book is for you. A great work by a fantastic author.</p></li><li><p><em>Thinking Forth</em> by Leo Brodie. I used to have this habit where I learned various programming languages. To challenge my perspective, I chose languages from families and paradigms that were fundamentally different from what I already knew. Occasionally, I stumbled upon greatness. <em>Thinking Forth</em> presents programming as a creative process, and it delivers those lessons in an entertaining and pedagogical way. The book is particularly strong on software analysis and design, but it&#8217;s the execution and technical writing that brought the book to my top five. This is <em>the</em> book for anyone writing about software &#8212; Leo&#8217;s way is the way to do it.</p></li></ul><p>Since the ranking is zero-indexed, there&#8217;s one more book in the top 5:</p><ul><li><p><em>The Nature of Code: Simulating Natural Systems with Processing</em> by Daniel Shiffman. This book is a beautiful introduction to simulations of natural systems. We get concise prose, clear illustrations, and carefully chosen systems with a guided tour through the math. All done in the Processing language, which is fun and easy to pick up. The reason I like this book so much is probably due to timing. In 2013, I was on the verge of abandoning software development. I was fed up. (That&#8217;s a story for another time.) Instead, I planned to go into cognitive psychology full-time as a researcher. This book brought back the joy in programming for me. Sometimes, a good book is the book that motivated you.</p></li></ul><h3><strong>Honorable mentions</strong></h3><p>Picking a top five was hard. There are so many other enjoyable books, and on a different day, the list above might have included any of the ones below:</p><ul><li><p><em>The Art of the Metaobject Protocol</em> by Kiczales, Rivieres and Bobrow. This one is for those times when you need to bend your mind in interesting ways...</p></li><li><p>...and <em>Let Over Lambda</em> by Doug Hoyte is perfect for those three people in the world who find <em>The Art of the Metaobject Protocol</em> underwhelming. One of the most impressive books I&#8217;ve read.</p></li><li><p><em>The Design of Design</em> by Frederick P. Brooks. It never received the attention that <em>The Mythical Man-Month</em> got, yet <em>The Design of Design</em> shares many of its qualities. It&#8217;s a book that grows, and there&#8217;s a wealth of insights on offer.</p></li><li><p><em>The Little Schemer</em> by Friedman and Felleisen. This book won&#8217;t teach you Scheme. Rather, it teaches something more valuable: it teaches how to think recursively. Working through the book even made me comfortable with the <a href="https://en.wikipedia.org/wiki/Fixed-point_combinator#Y_combinator">Y-combinator in lambda calculus</a>. (At least for some time).</p></li><li><p><em>Multi-Paradigm Design for C++</em> by James O. Coplien. Twenty years after reading this, the core techniques are still with me. These are commonality analysis, where the purpose is to identify families of systems, and variability analysis, which focuses on capturing the domain parameters that vary. In essence: the foundation of great software design.</p></li></ul><p>Alright, I&#8217;ll try to stop here. Otherwise I get tempted to also throw in <em>The Timeless Way of Building</em>, <em>Programming Erlang</em>, <em>The Art of UNIX Programming</em>, and, and, and... No &#8212; promise: I stop right here. Let me just offer some parting words.</p><p>The books in this post might <em>use</em> a specific programming language. But it would be a mistake to think that they <em>are</em> about that language. Their lessons are wider, much wider. They demonstrate better ways to think about software.</p><p>So when reading these books, we shouldn&#8217;t focus on the specifics. It&#8217;s all about the general lessons. And those transcend the Lisp, C++, Smalltalk, or Forth used in  coding examples. To me, that&#8217;s the hallmark of truly great books.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe to support my work on practical patterns, research, and reflections on coding in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Why Human-Level AI Won't Be Enough]]></title><description><![CDATA[What if today's best engineers aren't enough for tomorrow's systems? By following today's trends, we may discover that human-level code quality was never the problem we needed to solve.]]></description><link>https://adamtornhill.substack.com/p/why-human-level-ai-wont-be-enough</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/why-human-level-ai-wont-be-enough</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 30 Jun 2026 05:30:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ukqg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Having an AI produce code at the quality of a human expert has been the dream and definitive benchmark for years now. Recent progress on both foundation models and, perhaps more important, the agentic harnesses have made this dream seem less distant.</p><p>Let&#8217;s assume we (or BigTech) succeed: tomorrow&#8217;s coding agents perform at the level of today&#8217;s best software engineers. My argument is that this still wouldn&#8217;t be enough.</p><h2><strong>AI Raises the Quality Bar</strong></h2><p>A popular claim these days is that coding agents already write better code than many programmers. That&#8217;s probably true. And the bar keeps getting pushed.</p><p>So why would a coding agent performing at the level of a human expert still be inadequate?</p><p>The reason is that AI itself changes the quality threshold required to keep systems stable.</p><p>The most obvious change is that tomorrow&#8217;s systems will be much more complex than today&#8217;s. We just need to <a href="/__u/adamtornhill.substack.com/p/coding-is-dead-but-it-still-smells">look back at our history</a>. Each major productivity increase in software history has made us take on larger and more complex problems. Improved capabilities let us automate tasks we couldn&#8217;t automate before. <a href="https://en.wikipedia.org/wiki/Lehman%27s_laws_of_software_evolution">Lehman&#8217;s laws of software evolution</a> captured that force decades ago: useful software keeps changing, growing, and accumulating complexity unless we actively fight it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!ukqg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 424w, /__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 848w, /__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!ukqg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png" width="611" height="580.2378472222222" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1094,&quot;width&quot;:1152,&quot;resizeWidth&quot;:611,&quot;bytes&quot;:180702,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/200314817?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 424w, /__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 848w, /__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ukqg!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F296d8e88-c28c-4712-80ba-745647ed8a20_1152x1094.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Software systems keep changing, growing, and accumulating complexity.</figcaption></figure></div><p>Another change is that an agent is orders of magnitude faster than a human in terms of raw code generation. That speed causes <a href="/__u/adamtornhill.substack.com/p/why-merge-conflicts-became-the-new">problems already today</a>. And still: future systems are likely to evolve at a pace that is significantly higher than today where we still have plenty of constraints and manual checkpoints in our delivery pipelines.</p><p>Combine these two evolutionary pressure points, and you get codebases that few &#8212; if any &#8212; of today&#8217;s experts could stay on top of.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2><strong>Scale Changes the Problem</strong></h2><p>These pressure points also explain why human expert level is the wrong bar.</p><p>The first challenge is that defect opportunities scale with code volume and change volume. It&#8217;s a safe bet to claim that future systems will have that relationship, too. Larger modules, larger changes, and higher code churn have been linked to fault risk for decades.</p><p>Without a corresponding breakthrough in verification and validation techniques, a more ambitious and larger system developed at that breakneck speed by a future human-expert-level AI would be a fertile breeding ground for bugs and quality risks. We&#8217;d get to truly experience what it means to be wrong at scale.</p><p>Second, coding agents are based on a stochastic core. At their very heart, LLMs are prediction machines. Even if we manage to reduce their defect rate to something like the magical <a href="https://infinitesimallysmallcom.wordpress.com/2021/04/18/six-sigma-the-math-behind-it/">six sigma</a>, lessons from industrial production still tell us that the potential for error will always be there.</p><p>A tiny failure rate is manageable at human throughput. But generate enough code, and rare errors stop being rare at the system level. A one-in-a-million mistake sounds impressive...until you start making millions of decisions. All the time. That&#8217;s one consequence of scale.</p><p>So today&#8217;s bar is too low for tomorrow&#8217;s AI. AI changes the scale of the problem.</p><h2><strong>Embrace Imperfection</strong></h2><p>If human-expert code is insufficient, then quality, correctness, and fit need to be addressed with a different approach, too.</p><p>One possible response is to aim for superhuman code quality. Personally, I don&#8217;t think that&#8217;s a realistic path. Where would we even get the training data and feedback signal? Synthetic data, but from what and whom? The hardest software decisions are contextual, and rarely come labeled and packaged as reusable training examples. And yes, AGI has been &#8220;just a few years away&#8221; for the better part of fifty years. I wouldn&#8217;t hold my breath.</p><p>More importantly, human-level coding may not even be the problem we need to solve.</p><p>Today, my approach to taming agents has been to accept their unreliability. I suspect that direction holds the keys to the future of software quality, too.</p><p>So, perhaps, instead of aiming to generate perfect code, we&#8217;d need to double down on creating environments where unreliable agents reliably produce acceptable outcomes. The future might belong to those organizations that embrace that imperfection today.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe to support my work on practical patterns, research, and reflections on coding in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[An opinionated (and mainly correct) guide to naming]]></title><description><![CDATA[Naming in software goes way beyond any aesthetics. Good naming minimizes reconstruction work. Here is the style and habits that I've picked up.]]></description><link>https://adamtornhill.substack.com/p/an-opinionated-and-mainly-correct</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/an-opinionated-and-mainly-correct</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 23 Jun 2026 15:10:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/83b87138-98fa-41ad-b4ad-f42f8a7afc0a_1402x1122.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today we&#8217;re taking on the <a href="https://martinfowler.com/bliki/TwoHardThings.html">second hardest problem</a> in computer science: naming.</p><p>Each time we write code, we face a multitude of micro-decisions. How do we label functions, classes, variables, etc. to get the most out of the code as a communication medium? That is, communication that sharpens our thoughts in the moment, as well as code that supports our future selves and any agent trying to make sense of what&#8217;s already there.</p><p>As such, naming in software goes way beyond any aesthetics. Good naming minimizes reconstruction work. For both humans and machines.</p><p>In this article, I share the style and habits that I&#8217;ve picked up. It&#8217;s a style that evolved over the years, and shaped by viewing software through the lens of cognitive psychology to support the way we think, reason, and solve problems.</p><h2><strong>Why naming matters</strong></h2><p>Names are cognitive compression mechanisms. Design elements with strong names stretch the amount of information you can hold in your head at once.</p><p>Further, when reading unfamiliar code, we try to infer the purpose by building up mental representations. This process is largely driven by the names of classes and functions. The stronger the names, the easier the process.</p><p>And this translates into time savings, too. For example: clearer identifier names cut debugging times by <a href="https://link.springer.com/article/10.1007/s10664-018-9621-x">19% alone</a>.</p><p>AI-assisted development benefits too. A <a href="https://arxiv.org/html/2505.10443v3">recent study</a> investigated the structural elements that contribute to LLM code understanding. Improving identifier names consistently yielded the largest returns.</p><h2><strong>Optimize function names for calling context</strong></h2><p>Most naming advice focuses on the declaration site. However, we read call sites far more often than declarations.</p><p>Consequently, we should communicate context via names. Let the names combine to build sentences in the calling context:</p><pre><code><code>notify_all(registered_clients, about=the_new_version)</code></code></pre><p>Turning our function calls into sentences has cognitive reasoning benefits: that one sentence serves <a href="/__u/adamtornhill.substack.com/i/194769703/introduce-functions-your-brain-loves">as a chunk</a>. It becomes an abstraction that allows us to squeeze much more information into our cognitive working memory. That way, reasoning improves.</p><h2><strong>Derive names via wishful thinking</strong></h2><p>Now, let me share a trick that has helped me get this naming right over the years: the beauty of <em>Wishful Thinking</em>. Wishful Thinking in this context is a design tool that I learned about from the uber-classic <em>Structure and Interpretation of Computer Programs</em>. The idea is to write code as if the abstraction already existed. Pretend. Then, once the ideal sentence has taken shape, you go ahead and implement those functions. Naming comes first.</p><p>The reward is code that reads close to natural language without becoming verbose. That helps bridging the gap between the problem we&#8217;re trying to solve and the solution we express.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2><strong>Let domain types liberate your parameter names</strong></h2><p>A function name is incomplete without its arguments. Yet too much code misses that opportunity.</p><p>A common reason for that is the code smell <em>primitive obsession</em>: representing domain concepts using primitive types. Consider:</p><pre><code><code>public ActionResult ListRss(int languageId) {
...
}
public ActionResult NewsItem(int newsItemId) {
...</code></code></pre><p>While an <code>RSS</code> feed and a <code>News</code> item are clearly separate domain concepts, the code models both as raw integers. Besides weakening the type system, that code also fails to clarify context and intent. What roles does the news item play? Was it clicked by a reader, or selected to feature on a front page?</p><p>Introducing proper domain types lets us solve both problems. When the types capture the domain, they also liberate the variable names. Those names can now be repurposed to communicate context:</p><pre><code><code>public ActionResult ListRss(Language preferredRssFeedLanguage) {
...
}
public ActionResult NewsItem(NewsItem clickedArticle) {
...
}</code></code></pre><p>The types tell us what the arguments are. That frees the parameter names to tell us <em>why</em> they&#8217;re there.</p><h2><strong>Scope determines length</strong></h2><p>We get told that we should let our variables explain exactly what they do. This naturally leads to longer names. However, like all programming &#8220;rules&#8221;, the soundness of that advice depends on context. Names don&#8217;t carry meaning in isolation. They expand on their surrounding context.</p><p>So, variable naming should reflect scope. The smaller the scope, the shorter the variable name. (and vice versa).</p><p>As an example, consider the following:</p><pre><code><code>for i, article in enumerate(front_page_articles):
    publish(article)</code></code></pre><p>An <code>i</code> might be perfectly fine in that short, tight loop or in a lambda function. The whole block serves as one logical element, one chunk.</p><p>It also follows from the same principle that a one letter name is detrimental in a larger scope such as an instance variable or, shudder, a public API. There you really want to spell it out since the context cannot communicate the purpose of an <code>i</code> variable.</p><h2><strong>Naming conventions that detract</strong></h2><p>At this point, it&#8217;s time to look at what <em>not</em> to do.</p><h3><strong>Drop the I</strong></h3><p>Every now and then I travel outside my functional programming circle, and have to code in an object-oriented language. My object-oriented code follows the same style as presented here. This causes me to break one of the common &#8220;best&#8221; practices: the I-prefix added to interface names.</p><p>That is, I&#8217;d create a <code>ChatConnection</code> interface rather than an <code>IChatConnection</code>.</p><p>The fact that something is an interface is probably the least interesting aspect about that abstraction. I&#8217;d even argue that it&#8217;s a leaky name. Any user that gets hold of a reference to an instance of that type shouldn&#8217;t have to care <em>how</em> it&#8217;s implemented. Is it a concrete class? An abstract one? Or really an interface? Who cares? I don&#8217;t. So drop that cognitive distractor, the I-prefix.</p><h3><strong>Get rid of the getters</strong></h3><p>...and the setters.</p><p>We&#8217;ve probably all seen plenty of code along the lines of <code>getCustomer(id)</code>, or <code>setCustomerName(name)</code>.</p><p>Those prefixes, <code>get</code> and <code>set</code>, rarely add value. Rather, they detract by leading us down the dangerous path of asking rather than telling. (See the <a href="https://martinfowler.com/bliki/TellDontAsk.html">Tell, don&#8217;t Ask principle</a>). The names are procedural in their nature, and attract such code:</p><pre><code><code>customer = get_customer(customer_id)
set_customer_status(customer, SUSPENDED)</code></code></pre><p>The preceding logic is better modelled as:</p><pre><code><code>suspend(a_customer)</code></code></pre><p>And even if we stick to query-like functions, pure renaming makes the code read better:</p><pre><code><code>buyer = customer_for(customer_id)</code></code></pre><p>Disagree? Well, I said it&#8217;s an opinionated guide. There&#8217;s also a psychology lesson here, so let me throw in the <em>mere-exposure principle</em>. The mere-exposure principle says that repeated exposure tends to increase our preference for something. We like what we&#8217;re familiar with.</p><p>My advice is to continuously question our practices. Are they based on familiarity or reason?</p><h3><strong>Avoid the dumpster</strong></h3><p>One of the most common naming issues I tend to see are generic placeholder names like <code>Utils</code>, <code>Misc</code>, or <code>Helper</code>.</p><p>Names aren&#8217;t just passive labels. Just like the getters and setters, these names influence future behavior and perspective. A vague and imprecise name &#8212; like <code>Utils</code> &#8212; will attract code of those same qualities.</p><p>Each time we feel tempted to create a utility class, it&#8217;s an admission that we&#8217;re prepared to give up on our design. Resist the temptation, and iterate once more. Somewhere, there&#8217;s a domain concept looking to get out and get a proper name.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!NEha!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 424w, /__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 848w, /__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 1272w, /__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!NEha!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png" width="1402" height="1122" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1122,&quot;width&quot;:1402,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3301657,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/201257134?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 424w, /__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 848w, /__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 1272w, /__u/substackcdn.com/image/fetch/$s_!NEha!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2015a454-6611-42a6-a8b4-b067b8be4586_1402x1122.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A vague and imprecise name attracts code of those same qualities.</figcaption></figure></div><h2><strong>Good names reduce reconstruction work</strong></h2><p>This article reflects stuff I&#8217;ve learned over my three decades in software. The goal has always been to let the code communicate as effectively as possible.</p><p>That way, we guide future code readers, whether they are fellow humans or, increasingly common, coding agents. Both benefit from explicit intent and rich context. So, give your naming the focus it deserves.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Code for Humans and Machines is a reader-supported publication. To support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Hidden Design Decisions: Refactoring Control Coupling]]></title><description><![CDATA[Boolean flags often compress important design decisions into hidden behavior, leading to the classic problem of control coupling.]]></description><link>https://adamtornhill.substack.com/p/hidden-design-decisions-refactoring</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/hidden-design-decisions-refactoring</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 16 Jun 2026 05:28:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!yZBx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the worst things you can do to your code is to hide a design decision. In this article we&#8217;ll look at what happens when we compress design decisions into a boolean.</p><p>A boolean parameter looks harmless. It is just one extra argument, and inside the method it&#8217;s just one more branch. Yet that small boolean often hides a larger design problem: it forces the caller to select behavior.</p><p>That is the smell known as <em>control coupling</em>, first identified by Larry Constantine in the classic <em>Structured Design</em> from 1979.</p><p>Control coupling means one module controls the internal execution flow of another. Consider this example:</p><pre><code><code>public String incidentUpdate(Incident incident, 
                             boolean executiveAudience) {
    String severity = severityLabel(incident.severityLevel());
    String incidentReference = incident.incidentId() +
                               "@" + incident.service();
    String impactBand = impactBand(incident.impactedUsers());

    if (executiveAudience) {
        return "EXEC SUMMARY | incident=" + incidentReference
                + " | severity=" + severity
                + " | impact_band=" + impactBand
                + " | impacted_users=" + incident.impactedUsers()
                + " | owner=" + 
                      mitigationOwner(incident.mitigationOwner());
    }

    return "Engineering update for incident " + incidentReference
            + " in " + incident.region()
            + " is currently " + severity + ", impacting about " + incident.impactedUsers() + " users. "
            + "Impact band is " + impactBand + ". "
            + "Mitigation owner is " 
            + mitigationOwner(incident.mitigationOwner()) + ".";
}</code></code></pre><p>When a method takes a flag like <code>executiveAudience</code>, it couples the caller to the callee&#8217;s control flow in a way that is both brittle and easy to underestimate. This causes problems for both callers and the implementing class:</p><ul><li><p><strong>Implicit knowledge</strong>: The client must know that <code>true</code> means &#8220;use the executive version&#8221; and <code>false</code> means &#8220;use the engineering version&#8221;. That&#8217;s a weak API with hard-to-decode knowledge that does not belong at the call site.</p></li><li><p><strong>Low cohesion</strong>: Mostly, control coupling is just the messenger telling us that we are packing multiple concerns into a single method. This in turn leads to code that&#8217;s harder to reason about and fragile to change.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!yZBx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!yZBx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2267364,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/200246581?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yZBx!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe982fbe9-79f5-4fe3-a7ae-a795c00325e4_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A weak API that requires implicit knowledge to select the appropriate path.</figcaption></figure></div><p>The cohesion problem is obvious in the preceding <code>incidentUpdate</code> method. Its name suggests composing an incident update, but that&#8217;s not what it does. It composes one of <em>two</em> different kinds of updates, each with its own policy and audience. Once those two responsibilities are forced into the same function, the caller has to participate in choosing between them.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><p>The solution is to make that choice explicit. We do that by turning to another classic design pattern -- <em>Strategy</em>.</p><p>The core intent of Strategy is to encapsulate interchangeable behavior behind a common contract so the choice can vary independently from the code that uses it. You see, a boolean flag is often a compressed strategy. Instead of compressing behavior into a boolean, we now name it and make the variation point explicit.</p><p>We transform the original code by first encapsulating each choice in distinct classes:</p><pre><code><code>// This is the API for our specific Strategy classes:
public interface IncidentAudience {
    String composeUpdateFor(Incident incident, 
                            IncidentNarrative narrative);
}

// All knowledge on how to compose for Executives goes here:
private static final class ExecutiveAudienceUpdate implements IncidentAudience {
    @Override
    public String composeUpdateFor(Incident incident, 
                                   IncidentNarrative narrative) {
        return "EXEC SUMMARY | incident=" + narrative.incidentReference()
                + " | severity=" + narrative.severity()
                + " | impact_band=" + narrative.impactBand()
                + " | impacted_users=" + incident.impactedUsers()
                + " | owner=" + narrative.mitigationOwner();
    }
}

// ...and this class encapsulates knowledge of how to 
// communicate with Engineers:
private static final class EngineeringAudienceUpdate implements IncidentAudience {
    @Override
    public String composeUpdateFor(Incident incident, 
                                   IncidentNarrative narrative) {
        return "Engineering update for incident " 
                + narrative.incidentReference()
                + " in " + incident.region()
                + " is currently " + narrative.severity() 
                + ", impacting about " 
                + incident.impactedUsers() + " users. "
                + "Impact band is " + narrative.impactBand() + ". "
                + "Mitigation owner is " 
                + narrative.mitigationOwner() + ".";
    }
}

// Now we create the strategy objects.
// We expose the existing audience objects as a 
// convenience for callers.
// That gives us a simple first refactoring step.
// This is a safe step as long as the strategy objects are stateless:
public static final IncidentAudience EXECUTIVE_AUDIENCE = new ExecutiveAudienceUpdate();
public static final IncidentAudience ENGINEERING_AUDIENCE = new EngineeringAudienceUpdate();
</code></code></pre><p>Yes, there is a bit more code than before. So what do we gain?</p><p>The real payoff appears in the original function, which can now simply delegate to the selected audience:</p><pre><code><code>// The original method is now significantly simpler 
// as it only delegates to the strategies that 
// encapsulate the concept that varies.
public String incidentUpdate(Incident incident, 
                             IncidentAudience receiver) {
    IncidentNarrative narrative = narrativeFor(incident);
    return receiver.composeUpdateFor(incident, narrative);
}
</code></code></pre><p>In the original version, the method itself selected behavior internally based on a flag. The refactoring shifts that responsibility to the caller, who now selects an <code>IncidentAudience</code>. The branch disappears because the design now models the distinction directly.</p><p>The improvement is immediate at the call site:</p><ul><li><p>Before: <code>incidentUpdate(incident, true)</code>.</p></li><li><p>After: <code>incidentUpdate(incident, EXECUTIVE_AUDIENCE)</code>.</p></li></ul><p>The first forces the reader to remember what <code>true</code> means. The second communicates policy and explicit intent.</p><h3><strong>Bonus: Simplifying the Contract</strong></h3><p>There is a broader lesson hidden in this refactoring.</p><p>As part of the refactoring, we did the following move to introduce a simple domain type:</p><pre><code><code>private static IncidentNarrative narrativeFor(Incident incident) {
    return new IncidentNarrative(
            severityLabel(incident.severityLevel()),
            incident.incidentId() + "@" + incident.service(),
            impactBand(incident.impactedUsers()),
            mitigationOwner(incident.mitigationOwner())
    );
}

private record IncidentNarrative(
        String severity,
        String incidentReference,
        String impactBand,
        String mitigationOwner
) {}
</code></code></pre><p>Let&#8217;s compare how this type impacts the code:</p><pre><code><code>// We went from this original code:
public String incidentUpdate(Incident incident, 
                             boolean executiveAudience) {
        String severity = severityLabel(incident.severityLevel());
        String incidentReference = incident.incidentId() + 
                                   "@" + incident.service();
        String impactBand = impactBand(incident.impactedUsers());
        // -- implementation --
}
// ...to this more expressive version that introduces 
// a basic domain type:
public String incidentUpdate(Incident incident, 
                             IncidentAudience receiver) {
    IncidentNarrative narrative = narrativeFor(incident);
    return receiver.composeUpdateFor(incident, narrative);
}
</code></code></pre><p>Introducing the domain type <code>IncidentNarrative</code> is a move in the right direction because it makes the strategy contract clearer. Each audience now receives the source incident plus a small domain object containing the shared interpreted facts. That keeps the strategies focused on message shape rather than repeated preparation work. It sharpens the boundary of the new design.</p><p>Of course, our string-heavy data record may not win any beauty contests. But it is still a clear improvement over the original.</p><p>We could obviously have introduced domain primitives for the other concepts like <code>severity</code> and <code>owner</code>, too. But I&#8217;d rarely do that in the initial refactoring iteration, and I want these articles to highlight the often imperfect intermediate steps of reshaping existing code.</p><p>Perfection is the enemy of getting things done. Especially in software design. The important part is to move the code in a direction where the next change becomes easier to reason about than the previous one. And this refactoring did just that.</p><h3><strong>Why this helps human review</strong></h3><p>Flag arguments force the reader to mentally split a method into hidden modes, causing friction that accumulates across a codebase.</p><p>Strategies replace that hidden mode switch with named behavior that tells us directly what the code does.</p><p>The additional introduction of a domain type helps for the same reason. It turns a cluster of derived values into one named domain concept, which reduces the amount of detail a reviewer must juggle while reading the implementation.</p><h3><strong>Why this matters in AI-first development</strong></h3><p>Boolean flags are cheap for humans to write and expensive for models to interpret.</p><p>A boolean flag carries little semantic information on its own. The model has to scan additional code and context to infer what behavior the argument selects. That is a weak interface.</p><p>By contrast, named strategy objects like <code>EXECUTIVE_AUDIENCE</code> and <code>ENGINEERING_AUDIENCE</code> expose meaning directly at the call site. The model can immediately see which policy is being used. That improves local reasoning, narrows edit scope, and makes automated refactoring safer.</p><p>The same applies to the introduction of the domain type. It&#8217;s a refactoring that not only encapsulates data, but also raises the semantic abstraction level by aligning behavior with the domain concept it belongs to. That delivers explicit intent by explaining the code&#8217;s purpose structurally.</p><p>As is often the case with strong refactorings, our code ended up supporting the Open-Closed Principle. If we need a new audience later, we add a new <code>IncidentAudience</code> implementation rather than reopening a long method and editing its internals. That matters to an AI for the same reason it matters to humans: extension becomes more local and less risky.</p><h3><strong>The power of programming languages: implementation choices</strong></h3><p>The refactoring in this chapter uses small classes to model the strategies. But that is not the only way to implement the idea.</p><p>In functional programming languages, where functions are first-class citizens, strategies are created with even less ceremony. This means a call site can stay explicit while remaining very small. Here&#8217;s how it would look in Clojure:</p><pre><code><code>;; Use partial function application to pre-bind the incident to the update:
(def update-on-incident-for (partial incident-update incident))

;; Now we can produce updates with a minimum of syntactic ceremony:
(update-on-incident-for executive-audience)
(update-on-incident-for engineering-audience)
</code></code></pre><p>In Java, C#, or C++, we can use a similar lightweight variant by representing each strategy as a method reference instead of a dedicated class. That saves some implementation classes. The tradeoff is mostly one of readability and expressiveness: method references are lighter, but named classes can carry domain meaning more clearly when the policies deserve first-class names.</p><p><em>Recommendation</em>: use lightweight method references for internal strategy objects that aren&#8217;t exposed via the class&#8217;s public API. Prefer a proper class hierarchy for public APIs, avoiding any syntactic noise.</p><p>This implementation variant leads us to an important point: a design pattern doesn&#8217;t prescribe a specific class hierarchy or implementation technique. The preceding strategy implementations all stick to the original design idea. Rather, the important part of a pattern is its intent and trade-offs. Use the implementation form that is best suited to what the problem calls for.</p><h3><strong>One more step toward AI-readable code</strong></h3><p>This refactoring demonstrates several of the CLEAR principles in practice.</p><ul><li><p>Replacing the boolean flag with explicit strategies improves <em>Local Reasoning</em> by making behavior visible at the call site.</p></li><li><p>Discovering and naming the concepts that vary strengthens <em>Conceptual Alignment</em>.</p></li><li><p>Exposing behavior structurally improves <em>Explicit Intent</em>.</p></li><li><p>Decoupling the specific from the general helps <em>Reduce the Edit Surface</em> for future extensions.</p></li></ul><p>Like the <a href="/__u/adamtornhill.substack.com/s/ai-readable-code">other refactorings in this series</a>, the goal is to make the code more explicit about the problem it solves. That benefits both humans and machines.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe to support my work on practical patterns, research, and reflections on coding in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[CLEAR: Software Design Principles for the Agentic Age]]></title><description><![CDATA[Traditional design principles optimize for human maintainability. CLEAR optimizes for safe evolution through explicit structure under AI-assisted change.]]></description><link>https://adamtornhill.substack.com/p/clear-software-design-principles</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/clear-software-design-principles</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 09 Jun 2026 05:25:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d946f8b7-0a5d-4a2f-8841-b2f7429a4423_2316x968.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past months, I&#8217;ve written a <a href="/__u/adamtornhill.substack.com/">series of articles</a> on AI-readable code. Each one explored a different design problem, refactoring pattern, and remediation strategy. But they all focused on the same question: what makes code easy for an AI to read, extend, and modify?</p><p>I wanted to capture those principles in a more memorable format. So, let me introduce <strong>CLEAR</strong>: a small set of design principles for AI-readable codebases.</p><h2><strong>Isn&#8217;t SOLID enough?</strong></h2><p>Yes, I know. We already have SOLID. But SOLID was created in a different era, with a different optimization target.</p><p>AI-assisted development introduces a new bottleneck: reconstruction work.</p><p>Think of a service. It can follow SOLID perfectly while still scattering one feature across ten files and three abstraction layers. A human can eventually piece that structure together. An agent has to infer it from local context. The less obvious our software design, the more fragile the task. At best, we &#8220;only&#8221; waste tokens. More often, we stare at the resulting merge nightmare and need to weed out bugs and mistakes. That&#8217;s exhausting.</p><p>Agents infer structure by search, tool use, and <s>guesswork</s> <em>statistical probabilities</em>. That means code can follow SOLID and still be difficult for an agent to reason about if intent, ownership, and change boundaries remain implicit.</p><p>CLEAR focuses on that problem.</p><h2><strong>Where do the principles come from?</strong></h2><p>The principles come from my experience of building both new AI-native products as well as extending older, more mature, codebases. Using 100% agentic coding.</p><p>On my <a href="https://codescene.com/blog/agentic-ai-coding-best-practice-patterns-for-speed-with-quality">company blog</a>, I&#8217;ve pointed out the need to safeguard AI-generated code. We need to erect similar guardrails for design and architecture.</p><p>However, the guardrails for design and architecture have to be mouldable and adaptable to the problem domain and its context. When it comes to architecture and design, there are thousands of potential solutions for each problem. Good design constrains the solution while balancing the trade offs.</p><blockquote><p>In fact, great software design is itself part of the safeguard system.</p></blockquote><p>Hence principles rather than rules.</p><h2><strong>The Five CLEAR Principles</strong></h2><h3><strong>C &#8212; Conceptual alignment</strong></h3><p>Align behavior with the domain concepts it belongs to.</p><h3><strong>L &#8212; Local reasoning</strong></h3><p>Enable reasoning from local context for humans and agents.</p><h3><strong>E &#8212; Explicit intent</strong></h3><p>Explain the code&#8217;s purpose structurally.</p><h3><strong>A &#8212; Avoid search luck</strong></h3><p>Similar problems should be expressed using consistent structures, patterns, and extension points.</p><h3><strong>R &#8212; Reduce the edit surface</strong></h3><p>Design to contain change by making boundaries explicit.</p><p>These five principles define AI-readable code:</p><blockquote><p>AI-readable code makes software safer for agents to evolve and cheaper for humans to verify.</p></blockquote><h3><strong>A Common Language for Evolvable Systems</strong></h3><p>The unifying goal behind these principles is to limit the blast radius during software evolution. Each principle tackles a different part of that challenge.</p><p><strong>Conceptual alignment</strong> and <strong>Explicit Intent</strong> align the code with the problem domain, making responsibilities visible in the structure itself. <strong>Local Reasoning</strong> asks how much context agents need to understand for a change. <strong>Avoid Search Luck</strong> is about guiding via consistent architecture and design patterns, whereas a principle to <strong>Reduce the Edit Surface</strong> focuses on limiting risk and verification effort by containing the resulting code changes.</p><p>So far, my articles focused on the first three, the <strong>CLE</strong>. My next posts will explore the <strong>AR</strong> part. Since aligning behavior with the problem domain is one of the hardest design challenges, I&#8217;ll revisit the C with deeper guidance and examples, too.</p><p>My ambition is to evolve and clarify these principles by demonstrating how they let us achieve better agentic coding outcomes. I do so by leaning into research on AI, LLMs, and software in general. We work in an opinionated field, and I always found that it pays off to go for proven fundamentals.</p><p>So this is not novelty for its own sake. It&#8217;s about prior work that becomes more important in an agentic context. Upcoming articles revisit classics like Tell Don&#8217;t Ask, the Law of Demeter, DDD, and Parnas&#8217;s Information Hiding through that lens. Along the way, we&#8217;ll also discard a few popular ideas that have run their course.</p><p>The CLEAR ideas were important back in the days of human programmers, too. Now that agents accelerate the pace of coding, the principles become vital. Software design is more important than ever. And any code that requires reconstruction work (and luck) to modify becomes progressively harder to evolve.</p><p>CLEAR is an evolving attempt at capturing guidelines for that reality.</p><h2><strong>Examples: CLEAR in practice</strong></h2><p>Let me end by paying homage to Linus&#8217;s immortal words: &#8220;Talk is cheap. Show me the code.&#8221;</p><p>As pointed out, the ideas behind CLEAR are present in many classic design principles. The following articles explore specific design examples through an agentic lens:</p><ul><li><p>Conceptual Alignment: <a href="/__u/adamtornhill.substack.com/p/make-the-domain-explicit-from-procedural">Make the Domain Explicit: From Procedural Mess to Local Reasoning</a></p></li><li><p>Local Reasoning: <a href="/__u/adamtornhill.substack.com/p/reveal-intent-in-complex-conditions">Reveal Intent in Complex Conditions</a></p></li><li><p>Explicit Intent: <a href="/__u/adamtornhill.substack.com/p/hidden-design-decisions-refactoring">Hidden Design Decisions: Refactoring Control Coupling</a></p></li><li><p>Avoid Search Luck: <em>Upcoming article</em> on why agents fail when similar problems are solved inconsistently across a codebase.</p></li><li><p>Reduce the Edit Surface: <em>Upcoming article</em> on designing systems where change stays local and verification manageable.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe for practical patterns, research, and reflections on coding in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Why Merge Conflicts became the new Agentic Bottleneck]]></title><description><![CDATA[Revisiting some techniques from Your Code as a Crime Scene in the light of agentic coding. Specifically, how a socio-technical fit becomes even more important now that agents are our actors.]]></description><link>https://adamtornhill.substack.com/p/why-merge-conflicts-became-the-new</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/why-merge-conflicts-became-the-new</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 02 Jun 2026 05:25:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!IF5W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the more surprising effects of agentic coding is that it reinforces the fundamentals of software engineering. The less we humans code ourselves, the more we need to optimize for design, architecture, and a socio-technical fit. Agents might not have minds, but coordinating multiple agents in the same codebase is non-trivial. It puts pressure on the software architecture.</p><p>A good example of that pressure is merge conflicts. Merge conflicts seem to have their heyday now. And teams are experiencing them at agentic speed.</p><h2><strong>Merge conflicts as a socio-technical signal</strong></h2><p>The reaction so far seems to be to double down on better PR tools. Stacked PRs, multi-queue systems for PRs, intelligent conflict resolution, and much, much more. It&#8217;s all interesting, but also clear that we&#8217;re patching symptoms, rather than fixing the root cause.</p><p>So, let me go out on a limb and claim that recurring merge conflicts are a sign of socio-technical problems. And it doesn&#8217;t matter that the social agent is an, well, <em>agent</em>.</p><p>Yes, the people organization plays a large part, too. Team boundaries, branching strategy (the less, the better...), and ownership all contribute. But the root cause is often technical. When that&#8217;s the case, no matter how you re-org, the fundamental problems stick. No org chart can dig you out of an architectural blob.</p><h2><strong>The secret to parallelizing work</strong></h2><p>As Fred Brooks pointed out in <em>The Mythical Man-Month</em>, software work stops scaling linearly once tasks depend on each other. Specifically, coordination work grows quadratically: the number of communication paths becomes n(n-1)/2, where <em>n</em> is the number of people involved. Work will stall, be it via merge conflicts, review bottlenecks, or &#8212; common in the past &#8212; &#8220;sync&#8221; meetings.</p><p>That works in reverse, too. Development work <em>can</em> - and should - be parallelized when it represents independent tasks with similar relevance and urgency. However, to be effectively parallelizable, those tasks typically have to be independent at the level of the problem domain.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2><strong>How software architecture supports development work</strong></h2><p>And that&#8217;s where architecture comes in: that independence has to exist in the code, too. This means aligning the code with concepts from the problem domain: separate features, workflows, or product capabilities also need separate homes in the solution design. Otherwise, chances are that two seemingly independent tasks end up touching the same part of the code.</p><p>Recurring merge conflicts are the canary telling you that there is a misalignment between a) the work you do, and b) the type of work your architecture supports.</p><p>We only need to look at the leaked Claude Code repo to see the problem. Look at virtually any interesting part, and you&#8217;ll see how behavior seemed to converge in the same places.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!ZIac!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 424w, /__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 848w, /__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!ZIac!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png" width="1456" height="1412" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1412,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:370660,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/199575067?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 424w, /__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 848w, /__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ZIac!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd737dd86-5b19-4cf3-adb6-4c63913ade69_1510x1464.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">When code lacks modularity, unrelated changes inevitably collide in the same function.</figcaption></figure></div><p>The preceding <code>extractToolStats()</code> is a perfect example of architectural convergence. Git analytics, diff calculations, telemetry, UX timing analysis, and more, all accumulate in the same orchestration flow.</p><p>This means unrelated changes inevitably collide in the same function, simply because the poor modularity gives many reasons for agents to touch not only the same file, but even the same function. Under those conditions, it&#8217;s no wonder that we run into merge conflicts.</p><h2><strong>An old problem in agentic clothes</strong></h2><p>Fair enough, this type of misalignment is not a new problem, either. If you scaled up a human team on top of the same architecture, you would run into the same coordination pain, as experienced by countless projects ignorant of Brooks&#8217;s Law. The difference is speed. When it comes to merge conflicts, GenAI truly delivered on the 10x promise.</p><p>That is also where <a href="https://pragprog.com/titles/atevol/software-design-x-rays/">behavioral code analysis</a> becomes interesting. If the same areas of the codebase keep attracting parallel work, recurring merge conflicts are a signal. They tell you where the boundaries in the code no longer support the work being done. The structure of the codebase might still look sound at a quick glance, but your change patterns tell a different story.</p><h2><strong>Shine a light on coordination costs in the code</strong></h2><p>Behavioral code analysis to the rescue; it reveals where parallel work repeatedly converges.</p><ul><li><p><strong>Hotspots</strong> show where the system attracts change pressure.</p></li><li><p><strong>Coordination analysis</strong> reveals where many contributors compete for the same areas.</p></li><li><p><strong>Change coupling</strong> exposes architectural boundaries that fail to support independent evolution.</p></li></ul><p>Together, those analyses make coordination costs in code visible.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!IF5W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 424w, /__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 848w, /__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 1272w, /__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!IF5W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png" width="890" height="894" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d017c75e-705f-45c7-a25a-62099147fc55_890x894.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:894,&quot;width&quot;:890,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:547671,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/199575067?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 424w, /__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 848w, /__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 1272w, /__u/substackcdn.com/image/fetch/$s_!IF5W!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd017c75e-705f-45c7-a25a-62099147fc55_890x894.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example of change coupling due to a hotspot God Class: tight coupling without obvious patterns, nor benefits.</figcaption></figure></div><h2><strong>Acting on behavioral data</strong></h2><p>A behavioral code analysis won&#8217;t tell you what to do. It&#8217;s evidence and priorities, leaving the resolution to you. Depending on what the analysis reveals, those next steps might be more or less painful.</p><p>Examples include:</p><ul><li><p>Heavy author congestion in a hotspot: Frequently, the root cause is technical. Lack of modularity, low local cohesion, with too many business responsibilities squeezed into the same module. When that&#8217;s the case, break the hotspot apart along domain boundaries.</p></li><li><p>Change coupling in a local cluster: A typical sign of low package cohesion. Here, the solution might be to combine code spaced out in various files into the same unit and/or package.</p></li><li><p>Change coupling across distinct architectural elements: This is a bad one. It usually indicates erroneous architectural boundaries, with the most common cause being a <em>technical</em> separation of concerns (thin: MVC or MVP style patterns), rather than a system where the building blocks communicate domain concepts.</p></li></ul><p>Granted, none of those fixes are quick. But absolutely necessary. Otherwise we&#8217;ll keep grasping for tools that can relieve symptoms while the disease keeps spreading. When that happens, those parallel agents are no longer delivering value faster. Instead, there&#8217;s a serial bottleneck in the system, and it becomes the limiting factor.</p><h2>References</h2><p>The behavioral code analysis techniques are captured and described in:</p><ul><li><p><a href="https://pragprog.com/titles/atevol/software-design-x-rays/">Software Design X-Rays: Fix Technical Debt with Behavioral Code Analysis</a></p></li><li><p><a href="https://pragprog.com/titles/atcrime2/your-code-as-a-crime-scene-second-edition/">Your Code as a Crime Scene</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe to support my work on practical patterns, research, and reflections on coding in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[A Blast from the Past: SDD and the Illusion of Known Scope]]></title><description><![CDATA[Implementation was never just typing. It's discovery and learning. Tooling changed, human problem solving didn&#8217;t.]]></description><link>https://adamtornhill.substack.com/p/a-blast-from-the-past-sdd-and-the</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/a-blast-from-the-past-sdd-and-the</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Thu, 28 May 2026 05:30:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BoGG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the most fascinating aspects of the hyper-modern agentic era is the return of older attempts at taming software development. The latest trend to resurrect goes by the name of Spec-Driven Development (SDD). SDD does stand out as a revival of the belief that code is a mere artifact.</p><p>This article explores SDD through the lens of someone who lived through modelling days past. I acknowledge that &#8220;this time it might be different&#8221;, so I&#8217;ll focus my concerns on the flawed idea that implementation is the mere execution of a known scope. Rather, implementation is an essential part of the discovery process itself. And the further we remove ourselves from it, the harder it becomes.</p><h2><strong>Quick recap: what is SDD?</strong></h2><p>The idea with SDD is to capture the intended behavior in a structured specification. We then let a coding agent execute the resulting tasks and generate design docs, etc., in the process.</p><p>Yet, <a href="https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html">as Birgitta B&#246;ckeler points out</a>, SDD seems to come in several forms. It can be anything from a relatively lightweight way to drive agents to a strong form where the spec itself is the ground truth. It&#8217;s the latter I&#8217;m concerned with.</p><p>The risk is that SDD becomes the latest iteration of an age-old manager dream: have your skilled seniors specify what to build, then pass it on to the seemingly simpler &#8220;implementation&#8221; while pretending that step is predictable and somehow less important. In the 1990s, &#8220;implementation&#8221; meant a team of coders kept in the dark. Today, it&#8217;s obviously agents.</p><p>The process didn&#8217;t work well back then. As both the pace and expectations on a software delivery are orders of magnitude higher now, I&#8217;m sceptical about how well SDD will work this time around. Not due to waterfall thinking &#8212; many SDD practitioners evolve their systems iteratively &#8212; but rather due to the nature of problem solving.</p><h2><strong>The pull: a need for predictability</strong></h2><p>Software development is at a crossroads. The best possible outcome of the agentic revolution is that we raise the bar for software engineering. We know what&#8217;s good for us: strong automated test suites, continuous delivery, small increments, modular architectures, and top-notch code quality.</p><p>We also know that GenAI is stochastic. But so too is a group of humans collaborating on software. And if history taught us anything, the solution isn&#8217;t just better input but also faster feedback loops. Let me elaborate by travelling back in time to the model wars.</p><h2><strong>The Emperor&#8217;s new executable clothes</strong></h2><p>SDD is strongly reminiscent of the Model-Driven Architecture, Executable UML, and the glorious Rational Unified Process (RUP) of decades (fortunately) long gone.</p><p>Early in my career, I used these technologies at two different companies. In the first one, we had an effective design review process. As a developer, I&#8217;d basically sketch out the software design as a UML class diagram, complement it with some sequence diagrams for the dynamics, and did a walkthrough for my peers.</p><p>Yes, the process was slow. But what we designed usually worked as a direction on what to implement. The real advantage, though, was that the resulting UML diagrams served well for onboarding and extensions. It was easy enough to get the high-level view of the solution, which helped when later drilling into the code.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><p>So far so good. Then came the tool vendors. I mean, if you&#8217;ve already sketched out your design, why not take it full circle and generate all code from that sketch? After all, code is just an implementation detail, right? All that was needed was to extend UML with an &#8220;action language&#8221; to capture those &#8220;details&#8221;.</p><p>How well did it work in practice? Oh, the first victim was the documented design. The executable diagrams turned so bloated and verbose that they became literally incomprehensible. After all, the new audience was a compiler, not humans.</p><p>Changes and extensions got painful. You see, all those power tools we&#8217;ve gotten used to &#8212; build pipelines, command line utilities, IDEs, linters, etc. &#8212; didn&#8217;t exist in the vendors&#8217; design tools. Imagine coding in a Microsoft Word document. That&#8217;s how it felt.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!BoGG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!BoGG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1608851,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/198837000?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!BoGG!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64fca0-2b17-4898-8bb4-c2c5e1499a68_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A Model-Driven Architecture flashback. Some scars never heal.</figcaption></figure></div><h2><strong>The cognitive perils of a strong spec</strong></h2><p>Tooling was only part of the model-driven fiasco. A stronger problem was the process mindset at that time. Agile was brand new, and few people were familiar with the movement outside narrow programmer circles. Virtually every company I worked with operated in waterfall mode.</p><p>However, back then, the expectations on a software delivery were quite different. Today, with daily deployments being the standard, we just cannot afford to be slow in acting on feedback.</p><p>Human problem solving is inherently iterative and driven by reflection in action. We learn by doing. Express an idea in code, test it out, observe, and learn. The cycle expands our understanding of the problem we&#8217;re trying to solve. That improved understanding is then translated into modifications to the program, which we in turn observe and learn from. Rinse and repeat. We just cannot short-circuit that.</p><p>No, I don&#8217;t mean that we shouldn&#8217;t think things through early on. We should. And we should write stuff down to make it more concrete and invite a conversation. That helps thinking, too. But we need to treat that document as an imperfect starting point rather than the finished product.</p><h2><strong>The most underestimated aspect of code: requirements explosion</strong></h2><p>The main challenge starts if we work from a spec as if the problem was already understood. The reason is the concept of <em>requirements explosion</em>. The term was first coined by Robert Glass. Glass argues that &#8220;for every 10-percent increase in problem complexity, there is a 100-percent increase in the software solution&#8217;s complexity.&#8221;</p><p>In other words, each requirement in the spec will lead to tens of implicit design requirements that need to be resolved. We cannot leave that as guesswork for an agent to figure out.</p><p>To make SDD work, we would have to provide directives that let agents resolve a large share of all those implicit requirements.</p><p>So, couldn&#8217;t we complement our spec with a detailed solution model? We could. But it would lead us on a march with three major obstacles:</p><ol><li><p><strong>Free text lacks precision</strong>. Yes, we can explain constraints, etc., in text, but that becomes verbose and hard to check. Even structured prose and checklists leave room for ambiguity. That&#8217;s why we have programming languages. Those are excellent at capturing precise rules intended for machines.</p></li><li><p><strong>We cannot know the solution requirements up-front</strong>. Remember: we learn by doing. And if we don&#8217;t and rather have agents make those decisions for us, it becomes orders of magnitude harder to derive and capture the solution requirements. Think reverse engineering a legacy codebase. That&#8217;s the position we&#8217;d be in. Constantly.</p></li><li><p><strong>Extending requirements specs with implementation details and contracts blurs the model</strong>: This is what hit hardest back in the MDA/UML days: the model starts to become the implementation and loses its value as an overview, a different level of abstraction. Code just needs another level of precision.</p></li></ol><blockquote><p>The moment a model becomes the implementation, it ceases to be a good model.</p></blockquote><h2><strong>The other road ahead</strong></h2><p>SDD is something I&#8217;ll continue to observe but sit out on for now. However, that doesn&#8217;t mean I don&#8217;t value structure and a certain predictability. It just means we should look for those qualities elsewhere.</p><p>Predictable progress rests on domain expertise, intention-revealing software design, automated safeguards for our code and its behavior, and rapid visual feedback, all amplified by a highly skilled team. Those capabilities are harder to grow than a spec. But they are also far more valuable. With or without SDD.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe for practical patterns, research, and reflections on coding in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Make the Domain Explicit: From Procedural Mess to Local Reasoning]]></title><description><![CDATA[The more code hides, the more humans and agents have to reconstruct before making a safe change. In this article, we break apart a complex procedural method to optimize for reasoning.]]></description><link>https://adamtornhill.substack.com/p/make-the-domain-explicit-from-procedural</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/make-the-domain-explicit-from-procedural</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Thu, 21 May 2026 11:59:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!GUHO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI models do not &#8220;understand&#8221; code the way humans do. They infer meaning, and depend heavily on what the code communicates through names, boundaries, and structure.</p><p>This is also why <a href="/__u/adamtornhill.substack.com/p/how-long-should-a-function-be-and">long procedural methods make coding life harder</a> for agents. It&#8217;s not necessarily length per se, but rather that the longer the method, the more likely that it mixes multiple actions and responsibilities into one weakly described unit. That will confuse any agent.</p><p>However, detecting and recognizing a problem is only the start. The harder part is to act on it, and reshape the design in an agent-friendly way. As is often the case with software design, there&#8217;s an infinite number of potential paths. The proper choice depends on the problem at hand.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!GUHO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 424w, /__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 848w, /__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 1272w, /__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!GUHO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png" width="1456" height="842" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:842,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1271005,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/198254748?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 424w, /__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 848w, /__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 1272w, /__u/substackcdn.com/image/fetch/$s_!GUHO!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A conceptual map of where we are going with the refactoring in this article.</figcaption></figure></div><p>So far, we have looked at <a href="/__u/adamtornhill.substack.com/p/refactoring-express-selections-as">simplifying selection logic</a> and <a href="/__u/adamtornhill.substack.com/p/kill-the-conditional-maze-from-if">transforming long IF-chains</a> into rule pipelines. Now we&#8217;re going to expand our refactoring arsenal by taking on the problem of code that squeezes multiple side effects and business rules into the same long function.</p><p>Here&#8217;s our starting point: (Take a deep breath &#8212; it&#8217;s a long one)</p><pre><code><code>public void handleCase(
  BackofficeCase backofficeCase, 
  SideEffectPort sideEffectPort) {
    String normalizedTaskType = backofficeCase.caseType().trim().toLowerCase();

    if (normalizedTaskType.equals("refund")) {
        sideEffectPort.appendAudit("case:refund:" + backofficeCase.accountId());

        if (backofficeCase.amountCents() &lt;= 0) {
            sideEffectPort.appendAudit("refund:ignored_non_positive_amount");
            return;
        }

        if (backofficeCase.vip() &amp;&amp; backofficeCase.amountCents() &lt;= 20_000) {
            sideEffectPort.issueRefund(
                 backofficeCase.accountId(),
                 backofficeCase.amountCents());
            sideEffectPort.sendEmail(
                 backofficeCase.email(), 
                 "refund-approved-fast-track");
            sideEffectPort.appendAudit("refund:vip_fast_track");
        } else if (backofficeCase.hasOpenDispute()) {
            sideEffectPort.sendEmail(
                backofficeCase.email(), 
                "refund-needs-manual-review");
            sideEffectPort.appendAudit(
                "refund:manual_review_dispute");
        } else {
            sideEffectPort.issueRefund(
                backofficeCase.accountId(), 
                backofficeCase.amountCents());
            sideEffectPort.sendEmail(
                backofficeCase.email(), 
                "refund-approved-standard");
            sideEffectPort.appendAudit("refund:standard");
        }
        return;
    }

    if (normalizedTaskType.equals("welcome")) {
        // ...lots of code for the welcome flow...
        return;
    }

    if (normalizedTaskType.equals("ban")) {
        // ...lots of code for the ban flow...
        return;
    }

    if (normalizedTaskType.equals("export")) {
        // ...lots of code for the export flow...
        return;
    }

    sideEffectPort.appendAudit(
       "case:unknown:" + backofficeCase.caseType());
}
</code></code></pre><p>That&#8217;s a lot. The preceding code seems to handle different kinds of backoffice work. We see that a refund case issues money back and sends an approval email, whereas the welcome case would send onboarding material, and so on. The method is non-trivial.</p><p>Part of the challenge is that the outcomes of the distinct steps aren&#8217;t uniform values. Rather, they represent workflows and tasks that need to be performed.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><p>This is where the design pattern Command becomes useful. Instead of letting the method contain every possible choice, we encapsulate each business rule as a distinct executable unit.</p><p>The first step is to move the logic for each task into domain-named command objects:</p><pre><code><code>private static final BackOfficeTask PROCESS_REFUND = new ProcessRefund();
private static final BackOfficeTask SEND_WELCOME_PACKAGE = new SendWelcomePackage();
private static final BackOfficeTask EVALUATE_ACCOUNT_BAN = new EvaluateAccountBan();
private static final BackOfficeTask EXPORT_ACCOUNT_DATA = new ExportAccountData();
private static final BackOfficeTask HANDLE_UNKNOWN = new HandleUnknown();

private static final List&lt;BackOfficeTask&gt; DOMAIN_COMMANDS = List.of(
        PROCESS_REFUND,
        SEND_WELCOME_PACKAGE,
        EVALUATE_ACCOUNT_BAN,
        EXPORT_ACCOUNT_DATA
);

public void handleCase(BackofficeCase backofficeCase, 
                       SideEffectPort sideEffectPort) {
    String normalizedTaskType = backofficeCase.caseType().trim().toLowerCase();
    commandFor(normalizedTaskType).execute(backofficeCase, sideEffectPort);
}
</code></code></pre><p>We&#8217;ll go over the details and mechanism soon, but note how the offending <code>handleCase</code> method went from potentially hundreds of lines to just two lines of code. What happened?</p><p>Well, we introduced a small command model around the existing logic:</p><ul><li><p><code>BackOfficeTask</code> is a shared abstraction for executable pieces of backoffice work. A pure interface.</p></li><li><p><code>ProcessRefund</code>, <code>SendWelcomePackage</code>, <code>EvaluateAccountBan</code>, and <code>ExportAccountData</code> are concrete tasks, each owning one business rule family.</p></li></ul><p>Their implementation is straightforward:</p><pre><code><code>private interface BackOfficeTask {
    // purpose: selection -- is this the task to execute for the given input?
    boolean supports(String normalizedTaskType);

    // purpose: behavior -- encapsulates the logic and actions for a specific task.
    void execute(BackofficeCase backofficeCase, SideEffectPort sideEffectPort);
}

private static final class ProcessRefund implements BackOfficeTask {
    @Override
    public boolean supports(String normalizedTaskType) {
        return normalizedTaskType.equals("refund");
    }

    @Override
    public void execute(BackofficeCase backofficeCase, 
                        SideEffectPort sideEffectPort) {
        sideEffectPort.appendAudit(
            "case:refund:" + backofficeCase.accountId());
        // existing refund logic preserved here
    }
}
</code></code></pre><p>Like any design pattern, there aren&#8217;t any fixed rules or structure for what the implementation shall look like. Rather, we need to adapt the pattern to our context.</p><p>In this case, we do a simple linear search of the supporting command to match a given input task:</p><pre><code><code>private static final List&lt;BackOfficeTask&gt; DOMAIN_COMMANDS = List.of(
        PROCESS_REFUND,
        SEND_WELCOME_PACKAGE,
        EVALUATE_ACCOUNT_BAN,
        EXPORT_ACCOUNT_DATA
);

private static BackOfficeTask commandFor(String normalizedTaskType) {
        for (BackOfficeTask command : DOMAIN_COMMANDS) {
            if (command.supports(normalizedTaskType)) {
                return command;
            }
        }
        return HANDLE_UNKNOWN;
    }
</code></code></pre><p><code>DOMAIN_COMMANDS</code> is a list of known tasks, and <code>commandFor(...)</code> is the selector that finds the matching task for the current case type. This structure is a form of <a href="https://file+.vscode-resource.vscode-cdn.net/Users/adam/Documents/Jobb/MaatTechnologiesAB/Books/ai-readable-code/manuscript/link_to_chain_of_responsibility_pattern">responsibility chain</a> where each command decides if it applies. (It&#8217;s also an example on combining multiple patterns in one solution).</p><p>The <code>HANDLE_UNKNOWN</code> command acts as a safe default. It represents a variation of the Null Object pattern, ensuring that the system always has a valid command to execute, even when no specific case matches.</p><p>he original <code>handleCase(...)</code> method is now an orchestrator, delegating the actual work to the selected task instead of containing every branch itself:</p><pre><code><code>public void handleCase(BackofficeCase backofficeCase, 
                       SideEffectPort sideEffectPort) {
    String normalizedTaskType = backofficeCase.caseType()
                                               .trim()
                                               .toLowerCase();
    commandFor(normalizedTaskType).execute(
       backofficeCase, sideEffectPort);
}
</code></code></pre><p>That is the first benefit. Whereas the original method was organized around branching, our refactored version is organized around domain tasks.</p><blockquote><p>Note A natural next refactoring would be to remove the string-based selection entirely. We&#8217;ll do just that at the end of the article. For now, let&#8217;s bear this pain together.</p></blockquote><h3><strong>Why this helps human review</strong></h3><p>The long-method version forces the reader to keep several different concerns active at once.</p><p>While reading <code>handleCase</code>, you are not just tracking which branch applies. You are also tracking what kind of business action each branch performs and which side effects belong together. That is a bad fit for human working memory. Further, code that lacks cohesion also increases the risk for unexpected feature interactions: one branch changes a shared state, triggering downstream failures.</p><p>Distinct commands reduce that cognitive load by turning the large procedural mess into named chunks. The resulting command objects become cognitive units for reasoning. The reviewer can understand the dispatcher as one concern and each business rule as another. Future extensions are now likely to be additions rather than complex edits of a large block of code.</p><h3><strong>Why this matters in AI-first development</strong></h3><p>In the original method, the model has to infer that a cluster of statements represents a refund decision, a welcome flow, etc. The refactored code stops making the model reverse-engineer intent from branch shape by giving the code an explicit semantic structure. Those building blocks are now explicit domain actions.</p><p>The structure reduces ambiguity and guides both planning and modification. If an agent needs to change export behavior, <code>ExportAccountData</code> is the obvious unit to inspect. If it needs to reason about refund policy, <code>ProcessRefund</code> is the unit. The edit surface becomes narrower, and the risk of collateral changes drops.</p><p>Bringing the solution structure closer to the problem domain serves the translation from prompt to desired outcome.</p><p>This is the core idea behind refactoring towards AI-friendly code: make meaning explicit before asking the model to work with it.</p><h3><strong>Design guardrails</strong></h3><p>Use this refactoring pattern when a method coordinates several distinct actions with different side effects and/or workflows:</p><ul><li><p>Preserve business logic while moving code into commands.</p></li><li><p>Keep the public API unchanged during the initial transformation.</p></li><li><p>Name concrete commands by domain purpose, not architectural suffixes.</p></li><li><p>Let each task encapsulate its side effects.</p></li></ul><h3><strong>From domain actions to stronger API: evolving the design</strong></h3><p>Often, introducing explicit commands reveals further possibilities to simplify. As an example, take another look at our refactored code:</p><pre><code><code>public void handleCase(BackofficeCase backofficeCase, 
                       SideEffectPort sideEffectPort) {
    String normalizedTaskType = backofficeCase.caseType().trim().toLowerCase();
    commandFor(normalizedTaskType).execute(backofficeCase, sideEffectPort);
}
</code></code></pre><p>Right now, the commands are an implementation detail inside that class. That&#8217;s usually a good start, allowing us to optimize for local reasoning.</p><p>However, I often find the commands themselves might be part of a stronger API. So what if we start to expose these objects directly to the calling client?</p><pre><code><code>// refactoring note: we now accept a Task rather than a stringly typed 'case'
public void handleCase(BackOfficeTask taskToPerform, 
                       SideEffectPort sideEffectPort) {
    taskToPerform.execute(sideEffectPort);
}
</code></code></pre><p>In the preceding code we did just that: we shifted the API to accept a generic <code>BackOfficeTask</code> rather than having to create it ourselves. A nice side effect is that we get rid of the nasty task normalization with its complex and accidental string manipulations. (That is, getting rid of <code>backofficeCase.caseType().trim().toLowerCase()</code> &#8212; What&#8217;s not to like about that?)</p><p>The reason this usually works well as the next refactoring step, is because at the call site, context tends to be obvious; we <em>know</em> if we want a refund, send a welcome package, or export data. So why not take advantage of that contextual knowledge in the API responsbile for the corresponding actions?</p><p>As an added benefit, that improved API would also align with the Open-Closed Principle, meaning new clients can extend the program with new types of tasks without modifying the code processing them. Coding agents generally perform well in code with clear intent and a consistent structure that naturally communicates its extension points. The more explicit the structure, the less reconstruction work to perform.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe for practical patterns, research, and reflections on coding in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[How Much of my Writing is AI-Generated?]]></title><description><![CDATA[Writing is learning. LLMs remove that component.]]></description><link>https://adamtornhill.substack.com/p/how-much-of-my-writing-is-ai-generated</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/how-much-of-my-writing-is-ai-generated</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 19 May 2026 06:02:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DBBe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In early 2026 a barrier was crossed: the majority of LinkedIn posts were now AI-generated. <a href="https://originality.ai/blog/linkedin-ai-study-engagement">53.7%</a> to be specific. Other feeds like X or Reddit are unlikely to fare any better.</p><p>And now you&#8217;re reading this article. So a fair question is: is there a real person with their own experience and views behind it? Assuming the answer is &#8220;yes&#8221;, why would that matter?</p><p>What follows is my list of ingredients for Code for Humans and Machines. It explains how I work with AI as well as the consequences of outsourcing writing to it. Ultimately, it ties into the purpose of writing, which should go deeper than content production.</p><h2><strong>Does it matter if content is AI-generated?</strong></h2><p>Yes.</p><p>AI-written content has a peculiar flavour. LLMs employ several advanced stylistic devices. These give AI content its tell-tale signs, serving as mental cues to immediately scroll past. It&#8217;s not about quality per se. More like: If it was effortless to produce, it simply carries less value.</p><p>That reaction is not just me. Research shows that we rate identical works as more valuable when we think a human, rather than an AI, created them: <a href="https://doi.org/10.1186/s41235-023-00499-6">Humans versus AI: whether and why we prefer human-created compared to AI-created artwork</a>.</p><p>Feelings aside, my main problem with AI-generated content is that it is so bland. Yes, individual sentences can be expressive, even snappy. But the overall effect is repetition, verbosity, and a lack of sharpness. Impressive in the small, exhausting at length.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!DBBe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!DBBe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2547159,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/197685669?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!DBBe!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5402d990-7513-4366-a003-adeb07b82e11_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">At least the illustration is AI-generated.</figcaption></figure></div><h2><strong>Losing the benefits</strong></h2><p>Consuming LLM content is one thing. It poses an even larger challenge for the writer.</p><p>For decades, I&#8217;ve used writing to explore and understand various topics. Writing is an active process, and the very act of expressing an idea in text is itself a valuable way to learn.</p><p>I often start writing about topics I&#8217;d like to think I already master. Still, I frequently discover a gap in my understanding or an edge case I hadn&#8217;t considered. Writing is learning. LLMs take away that component.</p><p>That, too, is backed by research: writing is not just a way to report thought, but <a href="https://doi.org/10.58680/ccc197716382">a way to develop it</a>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2><strong>How much AI do I use?</strong></h2><p>There&#8217;s a crucial difference here: I do <em>use</em> AI a lot, but not to generate articles.</p><h3><strong>Iterating on sample code</strong></h3><p>Most of my articles contain code samples. Historically, those samples have been time-consuming. Not only do I have to find relevant examples; the code also needs to exhibit a specific design problem. Coming up with artificial code examples is hard enough. Coming up with examples that are &#8220;bad&#8221; in a specific way is next-level hard.</p><p>AI largely automates that process. I can iterate on sample code, instruct the agent to make it more or less complex, and even ask it to amplify some code smell for illustrative purposes. I love that.</p><h3><strong>Research assistance</strong></h3><p>I&#8217;ve always made it a point to base my recommendations on research. The real, peer-reviewed kind.</p><p>In the past, a lot of that work was repetitive searches on Google Scholar and university databases, trying to identify relevant papers. After skimming too many not-quite-what-I-need papers, reviewer fatigue would kick in.</p><p>An agent is, of course, a superb support for these types of tasks. I use AI a lot to narrow down my search. I probably spend the same amount of time in total, but now the bulk of that time is spent reading and understanding relevant material.</p><h3><strong>Review and feedback loop</strong></h3><p>This is where AI shines. It&#8217;s good at identifying inconsistencies, and offers a rapid feedback loop.</p><p>90% of my writing is rewriting. The first draft is usually quick, but I do multiple iterations where I tweak, clarify, and try to improve the flow and structure. Continuous feedback is useful.</p><h3><strong>Unblocking writer&#8217;s block</strong></h3><p>I have a tendency to get stuck on certain parts of a text. I know what my message should be, but I cannot get into the proper writing flow. Here, AI is wonderful: I type down a stream of consciousness and ask the AI to refine it.</p><p>Ultimately, I tend to throw most of that generated text away. Occasionally, I keep parts that I tweak. But getting a starting point &#8212; even if imperfect &#8212; helps more often than not.</p><h3><strong>Indirect influence</strong></h3><p>AI impacts my work in other ways, too. I mentioned earlier how LLMs mimic advanced stylistic writing patterns. Think of lines like &#8220;It&#8217;s not only an X. It&#8217;s a Y,&#8221; or the classic em dash.</p><p>I actively minimize my use of these tricks. The LLM tendency to overuse the em dash in particular is a tragedy &#8212; I employed it heavily in my pre-GenAI writing.</p><h2><strong>Why I write</strong></h2><p>Writing with AI support is a more enjoyable process. However, writing that is meant to be read is still 95% human effort. Any attempt to short-circuit that process comes with a cost: bland text and a lost learning opportunity. It&#8217;s self-defeating.</p><p>At the end of the day, the personal question each writer has to ask is:</p><blockquote><p>Why do I write?</p></blockquote><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe for practical patterns, research, and reflections on software design in the agentic era.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Reveal Intent in Complex Conditions]]></title><description><![CDATA[Extraction in itself is useless. Naming makes the difference. Here we refactor complex conditions to improve agentic coding.]]></description><link>https://adamtornhill.substack.com/p/reveal-intent-in-complex-conditions</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/reveal-intent-in-complex-conditions</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Thu, 14 May 2026 12:03:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BnIF!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ceb343-309b-4226-b2b2-387db9b14e27_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Complex conditions are cheap to write and expensive to read.</p><p>They usually start small: one <code>&amp;&amp;</code>, a few <code>||</code>,then one more clause to patch an edge case. Soon they have grown into little puzzles, but not for our amusement.</p><p>Every time someone reads such code, they have to reconstruct the intent from raw logic instead of seeing the business rule directly. What should be obvious becomes something you have to figure out.</p><p>Take this condition:</p><pre><code><code>String tail = originalName.substring(i).toLowerCase();
if (tail.startsWith("admin") || tail.startsWith("owner") || tail.startsWith("root")) {
    return true;
}
</code></code></pre><p>Looking at that code, we can of course derive its meaning. But only after mentally parsing each clause, hold the alternatives in working memory, and then reconstruct the intent. We probably did <em>not</em> read it and think:</p><blockquote><p>so simple &#8212; this checks for reserved role names</p></blockquote><p>That reconstruction friction is exactly what good refactoring should remove. Starting from code like:</p>
      <p>
          <a href="/__u/adamtornhill.substack.com/p/reveal-intent-in-complex-conditions">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Compressed Cognition: The Cost of Faster Coding]]></title><description><![CDATA[Agentic coding collapses the timeline of software decisions. What we gain in speed, we pay for in decision density and mental energy. Here's a deep dive into the trade-offs and how to work with them.]]></description><link>https://adamtornhill.substack.com/p/compressed-cognition-the-hidden-cost</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/compressed-cognition-the-hidden-cost</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Thu, 07 May 2026 06:02:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!livS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My post on <a href="/__u/adamtornhill.substack.com/p/coding-is-dead-but-it-still-smells">Coding Is Dead (...But It Still Smells Funny)</a> touched upon developer flow. One of the big wins with agents is that they let us stay with the higher-level problem for longer. We get less sidetracked by details, dependency cleanup, and similar secondary tasks that used to break concentration.</p><p>But there is a cost we are still underestimating. Agentic coding is mentally expensive.</p><p>I can usually sustain the pace for a couple of hours. Then I need a break. The pace is simply too intense. And based on conversations with other engineers, I do not think I am alone in that.</p><p>That is the trade I want to unpack here. Agents help us stay with the problem longer, but they also compress too many meaningful decisions into too little time.</p><h2><strong>The timeline collapse</strong></h2><p>Software development was always about making decisions. A typical product requirement quickly explodes into hidden design work that end users never see. Architecture, naming, boundaries, behavior, edge cases, test design, failure modes.</p><p>In the old days, say pre-2025, work unfolded at human speed, which meant the decisions were naturally spaced out. Today, agents compress the timeline. We have to deal with complexity and decisions that used to be spread over days in a single coding session.</p><p>But there&#8217;s more. <a href="/__u/adamtornhill.substack.com/p/welcome-to-code-for-humans-and-machines">Agentic coding raises the engineering bar</a>, and we just cannot afford to slip. Like skipping e2e tests, ignoring problematic dependencies, or rebuilding library functionality we should have reused.</p><p>So we&#8217;re now a) facing more complex work, with b) a compressed timeline of decisions. No wonder agentic coding is draining.</p><h2><strong>Decision density and mental energy</strong></h2><p>What we are experiencing as modern developers is not new. It just shows up in a new context.</p><p>Psychology has studied the mental cost of decision making for decades. One of the most well-known concepts is <em>decision fatigue</em>. Decision fatigue says that the quality of our decisions deteriorates after a long session of continuous choices.</p><p>One of the most cited examples comes from judicial decisions. <a href="https://www.pnas.org/doi/10.1073/pnas.1018033108">Danziger et al.</a> looked at thousands of rulings and found that favorable decisions dropped as judges moved through a work session. Interestingly, the decision quality then recovered after breaks.</p><p>The Danziger study shows what depletion looks like in practice. <a href="https://doi.org/10.1037/0022-3514.94.5.883">Other studies</a> suggest the mechanism: making choices is itself mentally expensive. Agents accelerate the arrival rate of those decisions.</p><p>Manual coding had a built-in pacing mechanism. That was our implicit recovery break. And it&#8217;s now gone.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!livS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 424w, /__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 848w, /__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 1272w, /__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!livS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png" width="1448" height="820" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:820,&quot;width&quot;:1448,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:905223,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/196201893?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 424w, /__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 848w, /__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 1272w, /__u/substackcdn.com/image/fetch/$s_!livS!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea3779e5-b115-4707-b1a1-b455581934c4_1448x820.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Why this is hard on the brain</strong></h2><p>This lines up neatly with cognitive load theory.</p><p><a href="https://onlinelibrary.wiley.com/doi/10.1207/s15516709cog1202_4">Sweller&#8217;s classic work</a> on cognitive load focused on problem solving. When too much information has to be held and manipulated at the same time, reasoning suffers. Agents might remove lower-level serial coding work, but they also increase the amount of high-level state you have to evaluate.</p><p>Working memory is limited too. You might have heard about the classic &#8220;seven plus or minus two&#8221; rule. Turns out that was over-optimistic. <a href="https://pubmed.ncbi.nlm.nih.gov/11515286/">Modern cognitive scientists</a> paint a more depressing picture: we can, at best, hold 3-4 things in our head at once and still be able to reason effectively.</p><p>Now, any non-trivial software task involves plenty of moving parts. Even with agents, we still have to decide which parts matter, how they interact, and whether the design still holds together. It&#8217;s a lot more architecture per minute.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2><strong>The interruption problem on steroids</strong></h2><p>We all know that traditional task switching is cognitively expensive. It harms our focus, and hurts performance.</p><p>However, what&#8217;s less known is that self-interruptions tend to be even more disruptive than external ones.</p><p>Coincidentally, agentic coding is like an open invitation for more self-interruption. The agent:</p><ul><li><p>asks a question,</p></li><li><p>produces a diff,</p></li><li><p>gets blocked by a missing tool,</p></li><li><p>fails a test, or</p></li><li><p>suggests a change that looks <em>almost</em> right but touches too much.</p></li></ul><p>Each event pulls you into a new review-verify-steer decision.</p><h2><strong>The AI productivity paradox</strong></h2><p>This also helps explain why the productivity story around AI coding tools is more complicated than the marketing slides suggest.</p><p>One of my <a href="https://arxiv.org/abs/2507.09089">favourite studies</a> gave experienced open-source developers access to AI tools. It was a controlled trial, so half the participants were still coding in the old school way.</p><p>At first glance, the study seemed to confirm the usual story: the developers with AI tools felt faster. Indeed, they claimed so themselves, estimating a 20% speedup. Only problem: they were not. They ended up 19% <em>slower</em> than the non-AI group. Adding insult to Big Tech injury, the study also demonstrates that even expert developers overestimate the AI impact on developer productivity.</p><p>One plausible contributor is self-interruption. Developers get many natural pause points: waiting, reviewing, correcting, etc. Each of those pulls you into a different task.</p><h2><strong>How I try to manage it</strong></h2><p>AI is now an integral part of our work, and we do not want agents to slow down. Speed is kind of their point.</p><p>But we do control when and how we actively interact with AI:</p><ul><li><p>The simplest tip is to keep agent tasks small and iterative enough so that the review fits in your head</p></li><li><p>That includes designing for cognitive resourcefulness: automate everything that can be automated. (You don&#8217;t want to spend time reviewing coding rules, checking test coverage, etc.)</p></li></ul><p>There are also things that I actively avoid doing:</p><ul><li><p><strong>Don&#8217;t review details, verify them.</strong> This was a hard one personally, but we need to accept that we can no longer know every line of code. Again, automation and safeguards are the mechanisms for delivering trust, not manual inspection.</p></li><li><p><strong>Avoid parallel work.</strong> I typically have one long-running agentic maintenance task that I just babysit, and then one focus task. Never more.</p></li></ul><p>That last point is important given the running-twenty-agents-in-parallel hype. I cannot even think about twenty <em>meaningful</em> things to build, and even less so about the resulting cognitive tax of the likely interruptions. It&#8217;s exactly the wrong thing to even consider. At least for humans. (And yes, I understand sub-agents and machine parallelisation. That is not what I&#8217;m objecting to. It is the parallelisation of human attention that does not scale).</p><p>Finally, and this will bring me enemies from the corporate ladder who think software is built by typing: take breaks from your agents. And take those breaks earlier than you think you need them.</p><p>Those breaks are not just recovery. They are also a chance to build <a href="/__u/adamtornhill.substack.com/i/195207789/developers-in-2027-two-emerging-paths">the skills the new developer role demands</a>. So use those non-coding hours to deepen your domain expertise. A few ways to do that:</p><ul><li><p><strong>Train as an end-user.</strong> This is the single best way towards becoming a domain expert. Master the problem domain.</p></li><li><p><strong>Grab a coffee.</strong> Many of the best ideas come <em>away</em> from the laptop. Your brain needs the occasional change of setting. Under the hood, it keeps operating. (The majority of all brain activity is automated and subconscious). When you relax, your brain delivers its insights that were worked on in the background.</p></li><li><p><strong>Do exploratory testing.</strong> Yes, automation is great, but there&#8217;s no limit to the creative ways we humans can break a system. Make it a challenge.</p></li><li><p><strong>Understand your product metrics and usage patterns.</strong> What features are people using, what are they ignoring, etc. This gives a different perspective on your code. Promise.</p></li><li><p><strong>Get involved in sales.</strong> This one might not be for everyone, but technical sales is a challenging role that gives you a much deeper understanding of where your product needs to be.</p></li></ul><p>The point is to re-invest the time agents save into activities that deepen your expertise while giving your brain a recovery window.</p><p>Agentic coding is still new. But it's becoming increasingly clear that the workflows around agents have to respect human cognitive limits. The future holds more software than ever, and we need to come prepared to build it in a sustainable way.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Code for Humans and Machines is a reader-supported publication. Consider to support my writing by subscribing.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Kill the Conditional Maze: From If-Statements to Rule Pipelines]]></title><description><![CDATA[Why long conditionals break both humans and agents, and how to refactor tangled if-chains into clear, composable rules that scale with change.]]></description><link>https://adamtornhill.substack.com/p/kill-the-conditional-maze-from-if</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/kill-the-conditional-maze-from-if</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 05 May 2026 10:31:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BnIF!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ceb343-309b-4226-b2b2-387db9b14e27_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Long chains of conditionals are one of the most common sources of accidental complexity. They&#8217;re a familiar sight in codebases, and rarely do they stay stable. Over time, those <code>if</code>-blocks turn into a sequence of intertwined decisions where order matters, intent is implicit, and change becomes risky.</p><p>This pattern addresses that problem by turning branching logic into an explicit pipeline of rules. Instead of hiding decisions in control flow, we make them visible, ordered, and easy to change.</p><p>The <a href="/__u/adamtornhill.substack.com/p/refactoring-express-selections-as">previous pattern</a> handled selection. This one handles workflows.</p><p>Consider the following code:</p><pre><code><code>// NOTE: Simplified example to illustrate the core issue.
// Real-world versions are typically much larger and harder to reason about.

// Admin accounts are usually safe, unless someone tries to sneak weird separators into the raw name.
if (context.accountType().equals("admin") &amp;&amp; !hasWeirdSeparators) {
    if (looksLikeImpersonation(normalizedName)) {
        logReview(100);
        return new ReviewResult(
                   "manual", 
                   "Admin-like username requires manual review");
    }
    auditRuleHit("ALLOW_SAFE_ADMIN_USERNAMES", context);
    return new ReviewResult("allow", "Admin username looks safe");
}

// 1. Block quoted fragments that may hide suspicious content
if (originalName.matches(".*\"[^\"]*\".*")) {
    logReview(101);
    notifyReviewQueue(context.userId(), "quoted-fragments");
    return new ReviewResult("manual", "Quoted fragments require manual review");
}

// 2. Block bracketed fragments that may hide tags or role names
if (originalName.matches(".*\\[[^\\]]*\\].*")) {
    logReview(102);
    String reason = originalName.contains("[admin]")
            ? "Bracketed role labels require manual review"
            : "Bracketed fragments require manual review";
    return new ReviewResult("manual", reason);
}

// ...think many more lines with conditionals and blocks...

if (normalizedName.contains("test") &amp;&amp; normalizedName.length() &lt; 8) {
    logReview(107);
    flagPattern(context.userId(), "short-test-username");
    return new ReviewResult(
               "manual", 
               "Short usernames containing 'test' require review");
}

return new ReviewResult("allow", "No suspicious username patterns detected");
</code></code></pre><p>So what&#8217;s the problem here?</p><p>The logic we are trying to express is a workflow. But the code does not say that. It hides the workflow inside a sequence of decisions, all compressed into a single method.</p><p>That style works right up until someone needs to change it. (And what good is code that an AI &#8212; or you &#8212; cannot safely change?) The consequence is that you cannot touch one rule without re-reading all the others, because the branching structure hides both order and intent.</p><p>The refactoring is simple: stop expressing policy as a branching maze and express it as an explicit decision pipeline.</p><p>After:</p><pre><code><code>private static final List&lt;ReviewRule&gt; REVIEW_PIPELINE = List.of(
        ALLOW_SAFE_ADMIN_USERNAMES,
        REQUIRE_MANUAL_REVIEW_FOR_QUOTED_FRAGMENTS,
        REQUIRE_MANUAL_REVIEW_FOR_BRACKETED_FRAGMENTS,
        REQUIRE_MANUAL_REVIEW_FOR_REPEATED_PUNCTUATION_BEFORE_DIGITS,
        REQUIRE_MANUAL_REVIEW_FOR_INVISIBLE_CHARACTERS,
        REQUIRE_MANUAL_REVIEW_FOR_RESERVED_ROLE_NAMES,
        REQUIRE_MANUAL_REVIEW_FOR_LEADING_OR_TRAILING_UNDERSCORE,
        REQUIRE_MANUAL_REVIEW_FOR_SHORT_TEST_USERNAMES
);

public ReviewResult reviewUsername(SignupContext context) {
    for (ReviewRule rule : REVIEW_PIPELINE) {
        Optional&lt;ReviewResult&gt; reviewResult = rule.tryReview(context);
        if (reviewResult.isPresent()) {
            return reviewResult.get();
        }
    }
    return allow("No suspicious username patterns detected");
}
</code></code></pre><p>What we have done here is adapt the classic <em>Chain of Responsibility</em> pattern to a refactoring problem. The original formulation comes from the Gang of Four book, where a request is passed along a chain of handlers until one of them decides to handle it (Gamma, Helm, Johnson, and Vlissides, <em>Design Patterns: Elements of Reusable Object-Oriented Software</em>, Addison-Wesley, 1994). Here, we use the same core idea, but in a stripped-down and more explicit form: a linear sequence of small rules.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Code for Humans and Machines is a reader-supported publication. Please join in.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>After this transformation, the logic and behaviour are driven by the table. Each rule in that table is simply a named method reference. Think of them as pointing to small rule methods. Here&#8217;s an example:</p><pre><code><code>private static final ReviewRule REQUIRE_MANUAL_REVIEW_FOR_QUOTED_FRAGMENTS =
        Reviewer::requireManualReviewForQuotedFragments;

private static Optional&lt;ReviewResult&gt; requireManualReviewForQuotedFragments(SignupContext context) {
    if (context.originalName().matches(".*\"[^\"]*\".*")) {
        logReview(101);
        notifyReviewQueue(context.userId(), "quoted-fragments");
        return Optional.of(manual("Quoted fragments require manual review"));
    }
    return Optional.empty(); // Optional.empty means: this rule does not apply, pass to next rule
}
</code></code></pre><p>Each rule becomes a small, named method that does one thing. It either returns a decision or passes control forward.</p><p>Also, the rules might not be just predicates. They also own their side effects. Logging, auditing, and notifications now live next to the decision that triggers them.</p><p>Note that we kept the original logic and structure (we only changed the return shape to <code>Optional</code> so the workflow can be driven by the pipeline). There is still cleanup we could do in those rule methods, but we do that stepwise: first make structure and intent explicit, then iterate once everything works. Too-large refactoring steps are a reliable way to sidetrack an agent.</p><h3><strong>Implementation Tip: Go Functional</strong></h3><p>If you prefer a more functional style, you can also express the orchestration as a pipeline operation:</p><pre><code><code>public ReviewResult reviewUsername(SignupContext context) {
    return REVIEW_PIPELINE.stream()
            .map(rule -&gt; rule.tryReview(context))
            .flatMap(Optional::stream)
            .findFirst()
            .orElseGet(() -&gt; allow("No suspicious username patterns detected"));
}
</code></code></pre><p>In C#, this maps naturally to LINQ; in Python, a generator-based first-match approach gives a similar shape.</p><p>I chose the explicit loop and <code>if</code> check in this chapter because it doesn&#8217;t require any detailed Java knowledge to follow, and because I prefer the pure simplicity of the more procedural form. Sometimes, a simple <code>if</code> is exactly what the doctor orders.</p><p>So the pipeline is not magic. It is just an ordered list of named decisions. The original logic is still there, but now encapsulated in stable and readable identities with their execution order controlled by the pipeline.</p><h3><strong>The Hard Part: Naming Rules</strong></h3><p>The challenging part in this refactoring is to identify the rules to extract and name. You might be fortunate to have comments explaining what the following code block does.</p><p>The original code has some of that: <code>// 1. Block quoted fragments that may hide suspicious content</code>.</p><p>But not everything is commented. Look at the final rule guarded by the <code>if (normalizedName.contains("test") &amp;&amp; normalizedName.length() &lt; 8) {</code> clause.</p><p>In the latter case, we need to go into detective mode and try to figure out the purpose and intent. (And the magic number <code>8</code> is not helping us here). Often, an LLM can help with the task -- language models are surprisingly good at naming things, a skill we humans struggle with.</p><h3><strong>Why this is better</strong></h3><p>In the conditional-heavy version, order is implicit in the branch clutter. In the refactored pipeline version, order becomes first-class data. You can point to the pipeline and answer immediately: &#8220;what runs first, what runs last, what short-circuits?&#8221;</p><p>That makes change less speculative. Add a new rule? Insert one pipeline entry and one method. Modify an existing rule? Open one dedicated method and stop there.</p><p>If that sounds familiar, it should. This is the same gain we saw in <a href="/__u/adamtornhill.substack.com/p/refactoring-express-selections-as">Express Selections as Tables</a>. There, we refactored branching logic into a declarative lookup table. Here, we refactor decision logic into a declarative pipeline. In both cases, behaviour stops being buried inside control flow and starts being expressed as explicit structure.</p><p>That means extension becomes simpler and safer. They are now declarative changes. That matters to an AI agent too: the code now advertises where behaviour lives, in what order it runs, and how it can be extended without guesswork.</p><h3><strong>When to use this pattern</strong></h3><p>Use responsibility chains when:</p><ul><li><p>one method contains many policy checks,</p></li><li><p>checks are mostly independent decisions,</p></li><li><p>rule order matters,</p></li><li><p>short-circuit behavior is desired.</p></li></ul><p>Do not force this pattern everywhere. If conditions share heavy mutable state, or if you are selecting behavior families, strategy/polymorphism may be a better fit.</p><h3><strong>Design guardrails</strong></h3><p>To keep this refactoring honest:</p><ul><li><p>Preserve API and behavior.</p></li><li><p>Keep the same decision order.</p></li><li><p>Keep each rule narrow and intention-revealing.</p></li></ul><p>A good litmus test is this:</p><blockquote><p>If you cannot describe what a rule does in one short sentence, the rule is probably doing too much.</p></blockquote><h3><strong>Why this matters in AI-first development</strong></h3><p>Conditional-heavy code consumes tokens without increasing information density.</p><p>LLMs have a limited attention budget. Every extra token competes for that budget, and unstructured control flow forces the model to spend it on reconstruction instead of reasoning. Such code burns more tokens than a Meta employee looking to land on the internal leaderboard.</p><p>Responsibility chains reverse that tradeoff. They convert implicit flow into explicit decisions. That reduces ambiguity, narrows edit scope, and makes automated transformations safer. All of that improves machine-readability:</p><ul><li><p>The orchestrator method is tiny, obvious, and stable.</p></li><li><p>Rule intent is encoded in method names, not inferred from nested conditions.</p></li><li><p>The agent can focus on one decision at a time instead of reconstructing control flow from a dense branch forest.</p></li></ul><p>In other words: this is not just a stylistic opinion. It is about better geometry for change.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe to receive new posts and support my writing.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Coding Is Dead (…But It Still Smells Funny)]]></title><description><![CDATA[The post-AI developer role and skillsets in the future of building software.]]></description><link>https://adamtornhill.substack.com/p/coding-is-dead-but-it-still-smells</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/coding-is-dead-but-it-still-smells</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Sun, 26 Apr 2026 13:03:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!QWRC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Just a year ago, it was still easy to dismiss AI coding as a shaky and inaccurate auto-complete. Now it&#8217;s clear to most software professionals that AI is not merely here to stay. It is fundamentally changing how we build software.</p><p>After more than 30 years as a programmer, I no longer code manually. Sometime in October 2025 I went 100% agentic, and I&#8217;m not planning on going back.</p><p>My agentic shift came after decades spent learning various programming languages, tools, and ecosystems. That&#8217;s a massive investment in skills that are now, at least partly, automated.</p><p>So what happens next? Will there even be such a thing as a software developer in a few years? Or will our profession go the way of the lamplighters and horse carriages?</p><h2>This Isn&#8217;t the Same Job Anymore</h2><p>Agentic workflows have moved us way past mere prompting and into something closer to delegation and orchestration. And that gives us a view of what lies ahead.</p><p>In the short term, two forces are pulling in opposite directions:</p><ol><li><p>Automation reduces the need for manual coding, suggesting <em>fewer</em> software people.</p></li><li><p>Yet expanding system complexity increases the need for <em>more</em> builders.</p></li></ol><p>My bet is on the second force exerting the strongest pull. There has yet to be a technological revolution that did not expand the scope of what we build. AI will not shrink software. The future will see more software than ever, as there&#8217;s an infinite &#8212; and growing &#8212; amount of tasks to automate via code.</p><p>The difference compared to earlier seismic shifts is the pace. The industrial revolution took roughly 80 years (1760-1840), giving society time to adapt. This time, we don&#8217;t get generations. We get months.</p><h2>The Illusion of Instant Software</h2><p>You probably see this in your LinkedIn and X feeds. They are filled with stories like building a Linux clone in Visual Basic by chatting to a Claude Code cluster over voice. While taking a bath. No prior coding experience. No deep technical background. Just a prompt and a generous supply of tokens.</p><p>Now, here&#8217;s the reality: Building the first prototype of a product or feature was always the easy part.</p><p>Roughly 95% of the cost of a software product occurs <em>after</em> the initial version is released. Turning a prototype into a stable, reliable, and evolvable system is where the real work starts. Shipping is optional. Maintaining is not.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!QWRC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 424w, /__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 848w, /__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 1272w, /__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!QWRC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:872162,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/195207789?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 424w, /__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 848w, /__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 1272w, /__u/substackcdn.com/image/fetch/$s_!QWRC!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7465e83b-03ac-48e3-a091-8c40a2e954b1_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Once you have users, you get pressure:</p><ul><li><p>new features,</p></li><li><p>bug fixes,</p></li><li><p>scaling concerns,</p></li><li><p>integration challenges,</p></li><li><p>and a constant need to evolve the system without breaking it.</p></li></ul><p>AI does not remove that complexity. Building successful production software has never been easy, and it won&#8217;t suddenly become trivial. Design, architecture, and what to build &#8212; the hardest parts &#8212; cannot be automated.</p><h2>The Objection: &#8220;AI will handle it eventually&#8221;</h2><p>But wait: did I really say that not everything will be automatable? I did. And that usually gets the same response:</p><blockquote><p>AI is improving rapidly. Soon it will be able to build and maintain complex systems on its own.</p></blockquote><p>Usually stated in a flat, matter-of-fact voice, as if that settles it.</p><p>It&#8217;s an appealing idea. I also believe it&#8217;s flawed&#8212;for at least three reasons:</p><h3>1. We keep raising the bar</h3><p>If we had stuck to the problems of the 1970s, programming would already be trivial. We didn&#8217;t. Instead, we created better tools, languages, and abstractions to take on bigger and more complex problems. AI will likely do the same.</p><p>Tomorrow&#8217;s systems will be even more ambitious.</p><h2>2. Many Real-World Systems aren&#8217;t AI-Ready</h2><p>Most real-world systems simply aren&#8217;t ready for autonomous agents. Long-lived legacy systems are shaped by years of implicit assumptions and accumulated technical debt while still being business critical.</p><p>For an agent to perform well, data quality is key. And in software, code is the data. Most production codebases are inconsistent, under-tested, and full of edge cases that even humans struggle to reason about. Sure, AI is useful on legacy systems, but fully autonomous development remains a pipe dream at best.</p><h3>3. You can&#8217;t bet your career on &#8220;soon&#8221;</h3><p>You just shouldn&#8217;t bet your career on the hopes and promises of &#8220;soon&#8221;. Even if full autonomy eventually becomes possible, the real question is: When?</p><p>We&#8217;ve been hearing that &#8220;AGI is around the corner&#8221; for years. Maybe it is. Most likely it isn&#8217;t. (Coincidentally, the AGI narrative is pushed by people who happen to sell language models...and rely on external funding for them).</p><p>Personally, I&#8217;m not waiting. I&#8217;m preparing for a world where the developer role changes. I&#8217;d rather be early than obsolete.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2>Coding is Dead</h2><p>Agents are automating the act of writing code. It&#8217;s easy to feel threatened. Uncertainty about one&#8217;s professional future is a big drain.</p><p>But here&#8217;s the good news: Code has no intrinsic value. It never had. (We just built our careers pretending it did.)</p><p>Code is a means to an end. What matters is what the code enables&#8212;the system, the behavior, the product. And this is where the human developer comes in. Because that human role does not disappear. It shifts.</p><h2>Developers in 2027: Two Emerging Paths</h2><p>I see two emerging and complementary developer roles:</p><p>First we have the <strong>expert generalist builder</strong>. In an AI-first workflow, the expert generalist is responsible for:</p><ul><li><p>deciding what to build</p></li><li><p>deciding how to build it (at the system level)</p></li><li><p>breaking work into meaningful increments that can be delegated to agents</p></li></ul><p>These responsibilities are not new. They have traditionally been split across:</p><ol><li><p>product managers</p></li><li><p>software architects</p></li><li><p>technical leads</p></li></ol><p>What&#8217;s changing is that these three specialist roles are collapsing into a single generalist role. No, it won&#8217;t be an easy job. Each one of those used to pay well in the past. Now one individual has to master them all. But for one salary.</p><p>Alongside that, I also see a second role emerging: <strong>the enabler</strong>.</p><p>The enabler is responsible for the meta-layer of development. The work that makes agentic development effective by:</p><ul><li><p>optimizing build and deployment pipelines</p></li><li><p>capturing constraints and patterns in machine-friendly formats (think: present day SKILLs)</p></li><li><p>improving testability and optimizing coverage</p></li><li><p>refactoring legacy systems to make them AI-friendly</p></li></ul><p>This role is not about shipping features. It is about enabling agents to build features well. As such, the enabler is a vital support role for the expert generalist.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!VWuh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 424w, /__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 848w, /__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 1272w, /__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!VWuh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png" width="1356" height="530" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:530,&quot;width&quot;:1356,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:90208,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/195207789?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 424w, /__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 848w, /__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 1272w, /__u/substackcdn.com/image/fetch/$s_!VWuh!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe082bc9b-2e24-4075-b561-f0798f322f6c_1356x530.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The two emerging and complementary developer roles.</figcaption></figure></div><h3>But what about all our hard-won knowledge?</h3><p>All your prior knowledge will serve you well in the AI age. Rather, the challenge is to keep on learning. Continuously.</p><p>To be an expert generalist, our learning and purpose needs to shift. Details aren&#8217;t as important as the larger picture. For example, it might be more important to know the type of problems and systems that are a good fit for the Rust language rather than mastering the borrow checker syntax. The former is a strategic tool, the latter a now automatable task.</p><p>Unfortunately, and these are the bad news, the future of development will probably not be for everyone. I met great programmers who code because they love to code. And now those days are gone, with no obvious financial incentive for bringing them back.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!2tQ3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 424w, /__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 848w, /__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 1272w, /__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_webp, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!2tQ3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png" width="1456" height="814" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:814,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:620133,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://adamtornhill.substack.com/i/195207789?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_424, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 424w, /__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_848, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 848w, /__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_1272, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 1272w, /__u/substackcdn.com/image/fetch/$s_!2tQ3!, /__u/adamtornhill.substack.com/w_1456, /__u/adamtornhill.substack.com/c_limit, /__u/adamtornhill.substack.com/f_auto, /__u/adamtornhill.substack.com/q_auto:good, /__u/adamtornhill.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff697d02b-da8d-49d6-9d7e-9f75af25cdac_2480x1386.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Code &#8212; a distant memory of days past?</figcaption></figure></div><p>But another group of developers are likely to thrive. As much as I loved to code by hand, I enjoy agentic coding even more. To me, code was always about what I could make it do, not syntax, not craft, definitely not the typing. Agents get me there faster.</p><p>My main worries early on were around just problem solving: When machines carry out our execution, how can we still stay in the loop to maintain effective mental models of the problem we&#8217;re working on? You see, problem solving is not a one-way street. It&#8217;s a constant iteration between observing, expressing, and reflecting.</p><h2>Agentic Coding: Supporting Flow</h2><p>Surprisingly, I&#8217;ve found that agents can support and strengthen that flow. In the past, I could easily get sidetracked by secondary tasks. Like restructuring the emerging code. I had an idea in my head, but now I wasn&#8217;t working on it; I was working on supportive and related tasks, but not the core. With agents taking over the details, I find it much, much easier to maintain the high-level thought and iterate on it.</p><p>That said, there&#8217;s a flip side to it.</p><p>Software development is definitely more mentally effortful with agents. </p><p>Maintaining a constant focus is draining, and I find that I can usually only sustain that intense high pace for a couple of hours before needing a break. (Thankfully, I can let a long-running agent make progress while I sip coffee).</p><h2>Final Thought: From Coding to System Design</h2><p>With AI, the developer&#8217;s role shifts from writing code to designing systems that can evolve safely, with agents doing the grunt work. But agents won&#8217;t replace human taste when it comes to deciding what to build.</p><p>Taste is a human quality. And building the right system is going to be key. Faster coding? Not so much. It&#8217;s already a commodity.</p><p>Even if code no longer serves as a moat or job security, strong engineering remains a competitive advantage. Teams that succeed in this shift to agentic development will iterate faster and build better systems. This is where systems thinking and domain expertise come in. Knowing what to build and how to build it matters more than execution.</p><p>Throughout my decades in various software companies, I&#8217;ve seen plenty of promising products fail. Not due to any flawed product ideas, but rather due to buggy, sub-par code preventing organizations to ship when the opportunity was rife.</p><p>If there&#8217;s one truly good thing coming out of the agentic revolution then it&#8217;s this: for many, bad code might become a distant memory of times less enlightened. But mark my words: an AI won&#8217;t set you up for that success on its own. Developers who fail to adopt are likely to ship the same sub-par code, only faster. Because the future of software development will not be less engineering. It&#8217;s <a href="/__u/adamtornhill.substack.com/p/welcome-to-code-for-humans-and-machines">more engineering at a higher level of abstraction</a>.</p>]]></content:encoded></item><item><title><![CDATA[Refactoring: Express Selections as Tables]]></title><description><![CDATA[We now turn from principle to practice. The first pattern addresses one of the most common causes of long methods: selection logic buried in conditionals.]]></description><link>https://adamtornhill.substack.com/p/refactoring-express-selections-as</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/refactoring-express-selections-as</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Thu, 23 Apr 2026 13:04:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BnIF!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ceb343-309b-4226-b2b2-387db9b14e27_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A surprising amount of code is just data pretending to be logic. It&#8217;s wearing a fake mustache.</p><p>Take a <code>switch</code> statement where each branch selects a value and returns it:</p><pre><code><code>switch (creature) {
    case DRAGON:
        return new SnackRecommendation("Charcoal-grilled marshmallows");
    case VAMPIRE:
        return new SnackRecommendation("Tomato juice on ice");
    case WIZARD:
        return new SnackRecommendation("Mystic instant noodles");
    default:
        return new SnackRecommendation("Chef special surprise");
}
</code></code></pre><p>When selection logic is encoded as branching, we force the reader to simulate the program to discover a simple fact: there is a direct mapping from input <code>X</code> to value <code>Y</code>. </p><p>A <code>switch</code> with a few case labels might not be the worst idea, but I&#8217;m confident that we&#8217;ve all seen switch-cases stretching over tens and hundreds of lines of code. Each time an agent wants to add a new case, it has to modify that function.</p><p>A better solution is to state the relationship between input and result value directly. A table does that:</p><pre><code><code>private static final SnackRecommendation RECOMMENDATION_FOR_MYSTERIOUS_GUEST =
        new SnackRecommendation("Chef special surprise");

private static final Map&lt;Creature, SnackRecommendation&gt; RECOMMENDATIONS_BY_CREATURE = Map.of(
  Creature.DRAGON,  new SnackRecommendation("Charcoal-grilled marshmallows"),
  Creature.VAMPIRE, new SnackRecommendation("Tomato juice on ice"),
  Creature.WIZARD,  new SnackRecommendation("Mystic instant noodles")
);

public SnackRecommendation snackFor(Creature aHungryOne) {
    return RECOMMENDATIONS_BY_CREATURE.getOrDefault(
            aHungryOne,
            RECOMMENDATION_FOR_MYSTERIOUS_GUEST
    );
}
</code></code></pre><p>This is not a Java-specific trick. In Python, the same idea is usually expressed with a dictionary. In C#, you&#8217;d typically use a <code>Dictionary&lt;TKey, TValue&gt;</code>. The syntax differs, but the pattern is the same: move the mapping out of control flow and into a data structure that states the relationship directly.</p><p>Refactoring to a table makes the design intent explicit: there is a fixed set of domain values, and each one maps to a recommendation. Better, any changes to the behavior of the code are now purely declarative. You modify the table, but don&#8217;t have to change any logic. That&#8217;s about as safe as it gets.</p><p>This refactoring has several benefits:</p><ul><li><p>Separates <em>selection data</em> from <em>selection mechanics</em>.</p></li><li><p>Removes repetitive branching noise that adds no new meaning.</p></li><li><p>Makes missing cases and defaults easier to reason about.</p></li><li><p>Gives both humans and agents a single, canonical place to inspect and modify.</p></li><li><p>Reduces the amount of code an AI must read before making a safe change.</p></li></ul><p>There is also a more subtle design gain here: once the selection becomes a table, it becomes easier to ask the right domain questions. Should this really be a fallback? Or should the default be an exception as a missing entry would indicate an internal bug? Those questions are much harder to see when the logic is buried in a <code>switch</code>.</p><h3><strong>When this refactoring applies</strong></h3><p>Use a table when branches are doing little more than selecting a value.</p><p>Typical signs:</p><ul><li><p>Each branch returns a constant or near-constant value.</p></li><li><p>The logic is keyed by a domain concept such as status, type, code, or state.</p></li><li><p>There is little or no branch-specific algorithmic behavior.</p></li></ul><p>In contrast, do <strong>not</strong> force everything into a table. If each branch contains meaningful behavior, side effects, or a non-trivial algorithm, then you probably have a behavioral variation problem rather than a selection problem. In that case, reach for polymorphism, composition, or dedicated strategy objects. (We&#8217;ll cover these patterns too later).</p><p>A blunt but useful rule is:</p><blockquote><p>If your <code>switch</code> mostly chooses data, use a table. If it mostly performs behavior, use objects or functions.</p></blockquote><h3><strong>Why this matters more in the age of AI</strong></h3><p>Humans are good at glossing over repetitive code. We see a <code>switch</code>, we skim, and we tell ourselves we got the idea. Often we did. Sometimes we did not.</p><p>Agents must process the structure more mechanically and literally. A long conditional construct increases the amount of code they need to ingest before they can infer the domain model. That increases token cost and the chance of error. The code becomes mechanically readable but semantically vague.</p><p>A table improves that situation because it is closer to the underlying purpose of the program. Instead of reading branches and reconstructing a mapping, the agent sees the mapping directly. This is one of the recurring themes in AI-readable code: refactor toward <em>explicit structure</em>. Make the code say what it is.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[How Long Should a Function Be? (And Why It’s the Wrong Question to Ask)]]></title><description><![CDATA[Function length is the wrong focus. What matters is how clearly your code communicates intent.]]></description><link>https://adamtornhill.substack.com/p/how-long-should-a-function-be-and</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/how-long-should-a-function-be-and</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Tue, 21 Apr 2026 08:01:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BnIF!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ceb343-309b-4226-b2b2-387db9b14e27_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most hard-to-understand code is not wrong. It is just structured in a way that hides its intent.</p><p>In many cases, the problem is not complexity itself, but where that complexity lives. We tend to express domain concepts through control flow&#8212;<code>if</code> statements, <code>switch</code> blocks, nested conditionals&#8212;rather than through explicit structure. That makes the code harder to read, harder to change, and harder for AI to work with.</p><h2>Design Right-Sized Functions</h2><p>Many a code reviewer has battled over function length. The argument usually alternates between two polar opposites: should we prefer many small methods, or is it better to keep related code in one large chunk?</p><p>Ultimately, that question misses the point. It is the wrong question to ask. Rather, we should focus on right-sizing our functions.</p><p>Fundamentally, the cost of working with code is dominated by how long it takes to answer questions like:</p><ul><li><p>What does this code do?</p></li><li><p>What will break if I change it?</p></li><li><p>Where should I make the change?</p></li></ul><p>Large functions increase that cost because they force us &#8212; and our agents &#8212; to scan, interpret, and simulate more logic before we can act. Right-sized functions reduce that burden. Not by being small, but by being <em>understandable</em>.</p><h3>Introduce Functions Your Brain Loves</h3><p>Human working memory is limited. We do not read code token by token&#8212;we read by <em>chunking</em>.</p><p>A good function acts as a cognitive unit. It lets us replace a block of logic with a single idea.</p><p>Compare:</p><pre><code>if (user.isVip() &amp;&amp; order.amount() &lt; 20000 &amp;&amp; !order.hasDispute()) {
    // ...
}</code></pre><p>with:</p><pre><code>if (isEligibleForFastTrackRefund(user, order)) {
    // ...
}</code></pre><p>It&#8217;s a simple example, yet the second version compresses detail into meaning. It allows the reader to move forward without simulating every condition.</p><p>That matters even more in an AI-driven workflow. As humans, we are no longer just authors of code. We are:</p><ul><li><p><em>orchestrators</em> and <em>technical leaders</em>, guiding agents toward the right implementation,</p></li><li><p>and <em>reviewers</em>, validating and correcting what those agents produce.</p></li></ul><p>Both roles depend on quickly understanding intent. Functions that align with how we think make that possible.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h3>Introduce Functions Your AI Agent Loves</h3><p>The same properties that help humans understand code also benefit AI systems&#8212;but for different reasons.</p><p>AI models do not &#8220;understand&#8221; code the way humans do. They infer meaning from patterns in tokens and depend heavily on what is explicitly expressed in the code.</p><p>Research shows that <a href="https://arxiv.org/html/2510.03178v1">naming plays a critical role</a>. When meaningful identifiers are replaced with arbitrary names, model performance drops significantly. Current models rely heavily on literal features&#8212;names, structure, and local context&#8212;rather than inferred semantics.</p><p>That has practical implications:</p><ul><li><p>A well-named function communicates intent directly.</p></li><li><p>A poorly named function forces the model to reconstruct meaning.</p></li><li><p>Larger, unstructured methods increase the amount of code the model must process before making a safe change.</p></li></ul><p>In other words, good function design reduces both cognitive load <em>and</em> token load.</p><h3>How long should a function be? Findings from research</h3><p>So, all this talk about right-sized functions. Couldn&#8217;t we just come up with a number? Sure. What about 24? That number comes from <a href="https://www.cs.ubc.ca/~rtholmes/papers/msr_2022_chowdhury.pdf">a 2022 study</a> which examined the evolution of &#8764;785K Java methods.</p><p>24 lines is lower than all corporate coding standards I&#8217;ve ever seen. Those tend to settle for variations of the idea that the whole function should fit on screen or, old school, a page of paper. (See the <a href="https://spinroot.com/gerard/pdf/P10exp.pdf">coding rules</a> from NASA&#8217;s Jet Propulsion Lab for a good example).</p><p>But even a number backed by data is misleading. Code doesn&#8217;t magically become unreadable once you add line 25. Size alone is a poor predictor of complexity.</p><p>Empirical research consistently shows that maintainability and understandability cannot be reliably inferred from method length. Such models are <a href="https://irinsubria.uninsubria.it/retrieve/b7ff6cde-3a3f-47be-b901-c8d3d4bca728/EMSE2023_understandability.pdf">not very accurate</a>. Instead, factors like naming, cohesion, nesting depth, and domain complexity are all more important.</p><p>With coding agents now working directly on our code, hard limits become even less useful. Agents benefit from explicit structure and clear intent, not from arbitrary line counts.</p><p><strong>&#127895;&#65039; There is no fixed number of lines that makes a function &#8220;good&#8221;.</strong></p><p>A long function is not a problem by itself. A function that mixes multiple concerns, hides intent, or resists naming is.</p><p>So the goal of refactoring is not shorter functions per se. It is better structure.</p><p>When we extract well-named functions with clear purpose, we introduce meaningful chunks into the design. Those chunks make it easier to reason about the system and often reveal better abstractions.</p><p>Refactoring is not about slicing code into smaller pieces. It is about discovering the <em>right</em> boundaries.</p><h4>Making it worse: split at random</h4><p>Discovering the right boundaries is not as simple as just extracting pieces of code into functions.</p><p>Consider this:</p><pre><code>// &#9888;&#65039; WARNING: do *not* try this at home
void processOrder(Order order) {
    impl1(order);
    impl2(order);
}

void impl1(Order order) {
    // first half of logic
}

void impl2(Order order) {
    // second half of logic
}</code></pre><p>This satisfies a line-count rule. It also keeps your company-mandated linting tool happy. What it doesn&#8217;t do is improve understanding.</p><p>Splitting functions based on arbitrary thresholds makes the design worse. Code that belongs together should stay together. Split by concept, never by lines.</p><h3>Beyond Method Extraction: define concepts</h3><p>Now, there&#8217;s one more thing to consider. Making a method shorter is not necessarily about splitting it into smaller ones. (In fact, I suspect that this misconception is a common reason for the keyboard wars around function length).</p><p>Consider:</p><pre><code>String allowedHumidityBand = request.allowedHumidityBand();
String[] humidityParts = allowedHumidityBand.split(&#8221;-&#8221;);
int minHumidity = Integer.parseInt(humidityParts[0]);
int maxHumidity = Integer.parseInt(humidityParts[1]);</code></pre><p>In the preceding code, there seems to be a domain concept involving humidity limits. But that concept is scattered across <code>String</code>s and <code>int</code>s. The reader has to infer that all of these values are really one thing: a humidity band.</p><p>A simple improvement is to define that concept directly:</p><pre><code>HumidityBand humidityLimit = parseHumidityBand(request.allowedHumidityBand());</code></pre><p>That is a small change, but it matters. Yes, we went from four lines of code to one, but length is secondary. The important part is that the code now aligns with the problem domain. The parsing details are encapsulated, so they no longer distract from the overall purpose of the method. You, and your agent, can now talk about a <code>HumidityBand</code> instead of passing around parsing artifacts and loose integers. That makes the intent clear.</p><h3>From Hidden Logic to Explicit Structure</h3><p>Functions are the first unit of structure in a codebase. They define how logic is grouped, how intent is communicated, and how change is localized. If the function boundaries are wrong, everything built on top of them becomes harder to understand and harder to evolve.</p><p>That is why function design matters so much. It is where structure begins.</p><p>It&#8217;s also where this series continues. In the next posts, we&#8217;ll take on specific patterns that turn tangled control flow into explicit structure:</p><ul><li><p>conditionals &#8594; named rules</p></li><li><p>branching &#8594; tables</p></li><li><p>logic blobs &#8594; composable pipelines</p></li></ul><p>If you enjoy taking messy code apart and rethinking its design, you&#8217;ll like what&#8217;s coming next. (And yes, we&#8217;ll break a few &#8220;best practices&#8221; along the way). </p><p>My next post is one of the most common problems: selection logic hidden inside conditionals. Subscribe if you want those patterns as they&#8217;re published, and see you soon!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Welcome to Code for Humans and Machines!]]></title><description><![CDATA[Designing software that remains understandable for humans while also being machine-legible and transformable by agents.]]></description><link>https://adamtornhill.substack.com/p/welcome-to-code-for-humans-and-machines</link><guid isPermaLink="false">https://adamtornhill.substack.com/p/welcome-to-code-for-humans-and-machines</guid><dc:creator><![CDATA[Adam Tornhill]]></dc:creator><pubDate>Sun, 19 Apr 2026 14:53:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BnIF!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5ceb343-309b-4226-b2b2-387db9b14e27_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>In the beginning</h1><p>In the beginning, there was code. Lots of it. And not all of that code was, shall we say, in the best possible shape. Then came the machines, first learning from that code, and then beginning to write it themselves.</p><p>Today, AI is transforming the software industry&#8212;and yet, we&#8217;re only getting started.</p><h2>Why code still matters</h2><p>Great code is no longer just readable. It must be machine-legible and transformable.</p><p>If AI cannot understand your code, you need to guide the agents with clear, structured, and concrete examples. This publication explores the principles, techniques, and refactoring patterns that shape how we should design software in the AI age.</p><p>Today, anyone can create a non-trivial software product. In fact, you can do so without knowing how to code. AI has come a long way.</p><p>However, writing the initial version of a product was never the key challenge. (That&#8217;s not to say it&#8217;s easy&#8212;there are enough infamous examples of systems that never shipped.) Most of the cost comes <em>after</em> the initial version is shipped, assuming you have product&#8211;market fit and customers.</p><p>The more successful your product, the stronger the pressure for new features and improved capabilities. This is where software design and architecture start to matter.</p><p>In 2026, my research colleagues and I published <a href="https://arxiv.org/pdf/2601.02200">a paper on AI-friendly code</a>. In short, we found that to do a good job, an AI agent demands better code quality than a human would.</p><p>Let&#8217;s repeat that, since it&#8217;s so important:</p><blockquote><p>To minimize defects, keep token costs reasonable, and ensure your AI implements the right thing, you need to care about code quality.</p></blockquote><h2>But won&#8217;t AI become good enough to work on any code?</h2><p>AI for coding has evolved at a ridiculous speed, and it&#8217;s tempting to believe that the future will be even brighter.</p><p>So instead of thinking about software design, perhaps we could all just relax and let time (and Big Tech) do its job?</p><p>I&#8217;ve been hearing that argument since early 2023. And so far, we&#8217;re not there.</p><p>It&#8217;s not an unsolvable problem, but several forces work against it:</p><ul><li><p>AI reflects its training data. Coding models are trained on real codebases&#8230;and most real codebases are not great.</p></li><li><p>LLMs lack an objective measure of what &#8220;good&#8221; looks like.</p></li><li><p>Without clear structure and intent, AI becomes directionless. It can &#8212; and will &#8212;modify code, but it cannot reliably understand it.</p></li></ul><p>That last point matters more than it might seem. When code does not communicate intent, every change becomes speculative, for humans and machines alike.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/adamtornhill.substack.com/subscribe"><span>Subscribe now</span></a></p><h2>The software engineering renaissance</h2><p>Fortunately, AI is still immensely useful even in the worst possible spaghetti code. It just needs direction.</p><p>And this is where you, the software professional, comes in.</p><p>Programming might be dead, but software design is more relevant than ever.</p><p>To perform well, agents need structure. That structure comes in the shape of:</p><ul><li><p><strong>Consistency</strong>: predictable structure, obvious places to look</p></li><li><p><strong>Documented principles and constraints </strong>(like <a href="/__u/adamtornhill.substack.com/p/clear-software-design-principles">CLEAR</a>)</p></li><li><p><strong>Reusable know-how</strong> (what we might call &#8220;skills&#8221; today)</p></li><li><p><strong>Alignment between problem and solution domains</strong></p></li><li><p><strong>No surprises</strong>: because surprises are expensive, for both humans and machines</p></li></ul><p>At this point you might wonder: weren&#8217;t these concepts important pre-AI? Absolutely. Great software has always been built on this foundation. </p><p>The difference in the AI era is speed: mistakes, ignorance, and shortcuts compound at machine speed. We can no longer get away with sub-standard architectures, nor can we compensate for poor design by throwing more people (or agents) at the problem. Clean code is not enough&#8212;we need great software design.</p><h2>What you&#8217;ll find here</h2><p>This publication is about making code easier to understand, safer to change, and more predictable to evolve by both humans and agents.</p><p>Much of that work comes down to structure.</p><p>A large part of the problem with poor-quality code is a lack of modularity and a failure to communicate intent. When code hides what it does, everything becomes harder:</p><ul><li><p>It is difficult to find the right place to make a change</p></li><li><p>It is risky to modify behavior without breaking something else</p></li><li><p>It is expensive for an AI to even <em>locate</em> the relevant logic</p><p></p></li></ul><p>This publication takes on that problem. It&#8217;s all about code that machines can safely transform.</p><h2>Agentic coding is harder, not easier</h2><p>Sometime in late 2025, I wrote my final lines of code. I wasn&#8217;t aware of it at the time, but that day marked a transition after almost four decades of programming. I&#8217;ve created and shipped plenty of code since then, but with one key difference: that code wasn&#8217;t written by hand. It was written by AI agents that I directed.</p><p>Surprisingly, I don&#8217;t long for the old days. I find agentic coding more rewarding, and the shift isn&#8217;t as large as I would have expected. It still feels like programming, just with larger steps and faster feedback.</p><p>That said, I&#8217;m grateful for those years of manual coding. Without that experience, I wouldn&#8217;t have been able to steer coding agents in the right direction, nor would I have known what and how to correct in the resulting software design (those corrections being agentic too, of course).</p><h2>Why this publication exists</h2><p>The short answer is that I simply love to write, and I enjoy developing software. Hopefully that shines through.</p><p>So what to I have to share? I&#8217;ve spent decades working with software systems across domains, paradigms, and organizations. Over time, certain patterns keep appearing. Some of those patterns transform a solution to make the code easier to work with, while others make the design more fragile.</p><p>In the AI era, those patterns matter even more.</p><p>In my next posts, I&#8217;ll share:</p><ul><li><p>refactoring patterns that improve structure and intent</p></li><li><p>design principles that make code easier to evolve</p></li><li><p>and occasional reflections on topics that interest me, for example what the developer role is becoming</p></li></ul><p>Because one thing is clear: The future of software development is not less engineering. It is engineering at a higher level of abstraction.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://adamtornhill.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Code for Humans and Machines! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item></channel></rss>