<?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: AI-Readable Code]]></title><description><![CDATA[Design principles with hands-on refactoring tutorials that make code easier and safer to change under AI-assisted development.]]></description><link>https://adamtornhill.substack.com/s/ai-readable-code</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: AI-Readable Code</title><link>https://adamtornhill.substack.com/s/ai-readable-code</link></image><generator>Substack</generator><lastBuildDate>Thu, 28 May 2026 20:20:16 GMT</lastBuildDate><atom:link href="https://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[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="https://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="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" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GUHO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 424w, https://substackcdn.com/image/fetch/$s_!GUHO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 848w, https://substackcdn.com/image/fetch/$s_!GUHO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13cb8758-944c-472a-ab3f-3e8ccf755471_1518x878.png 1272w, https://substackcdn.com/image/fetch/$s_!GUHO!,w_1456,c_limit,f_webp,q_auto:good,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="https://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="https://substackcdn.com/image/fetch/$s_!GUHO!,w_424,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 424w, https://substackcdn.com/image/fetch/$s_!GUHO!,w_848,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 848w, https://substackcdn.com/image/fetch/$s_!GUHO!,w_1272,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 1272w, https://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 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 role="img" 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><title></title><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="https://adamtornhill.substack.com/p/refactoring-express-selections-as">simplifying selection logic</a> and <a href="https://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="https://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[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="https://adamtornhill.substack.com/p/reveal-intent-in-complex-conditions">
              Read more
          </a>
      </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="https://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="https://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[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="https://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="https://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="https://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[<p></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="https://adamtornhill.substack.com/subscribe?"><span>Subscribe now</span></a></p><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><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></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><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="https://adamtornhill.substack.com/subscribe?"><span>Subscribe now</span></a></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>