<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Christie Cosky</title>
    <description>The latest articles on DEV Community by Christie Cosky (@christiecosky).</description>
    <link>https://dev.to/christiecosky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3800609%2F6fb89a16-0094-40ec-8ace-f7a267f83f78.jpg</url>
      <title>DEV Community: Christie Cosky</title>
      <link>https://dev.to/christiecosky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/christiecosky"/>
    <language>en</language>
    <item>
      <title>Understandable Systems Generate Evidence: How structure helps developers change code with justified confidence</title>
      <dc:creator>Christie Cosky</dc:creator>
      <pubDate>Tue, 09 Jun 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/christiecosky/understandable-systems-generate-evidence-how-structure-helps-developers-change-code-with-justified-24id</link>
      <guid>https://dev.to/christiecosky/understandable-systems-generate-evidence-how-structure-helps-developers-change-code-with-justified-24id</guid>
      <description>&lt;p&gt;&lt;em&gt;(The following example is fictionalized.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A notification template feature shipped six months ago. It let each tenant customize the messages sent to their own customers without requiring a back-end change every time the wording changed.&lt;/p&gt;

&lt;p&gt;The code reviewer could tell the design was hard to follow, especially the path from template to rendered value. But "this is hard to follow" is difficult to turn into a concrete objection when the feature works, the tests pass, and nothing is obviously unsafe or wrong. The design risk was real, but there wasn't an obvious bug to point to.&lt;/p&gt;

&lt;p&gt;QA signed off, and the feature went into production.&lt;/p&gt;

&lt;p&gt;Then a bug report came in: one customer had received a notification containing another customer's information. Somewhere in the notification pipeline, the system was leaking PII.&lt;/p&gt;

&lt;p&gt;At first, the fix sounded small: make sure notifications only render data belonging to the intended recipient.&lt;/p&gt;

&lt;p&gt;Then the assigned developer, who wasn't the original author, started looking for the place to make the fix.&lt;/p&gt;

&lt;p&gt;The templates were stored in the database. There were six template types, and each one populated its real values in a different part of the codebase. Some values came from customer-facing records, some came from internal workflow state, and some came from template-specific logic. The placeholder-to-value mapping lived somewhere else. Email and SMS channels shared part of the rendering path, but not all of it.&lt;/p&gt;

&lt;p&gt;Before the developer could decide where to fix the leak, they had to answer a more specific set of questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which placeholder rendered the wrong value?&lt;/li&gt;
&lt;li&gt;Where did that value come from?&lt;/li&gt;
&lt;li&gt;Which template types could use that placeholder?&lt;/li&gt;
&lt;li&gt;Did email and SMS resolve it the same way?&lt;/li&gt;
&lt;li&gt;What evidence would show that the leak was fully contained?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system was hard to change because it made the behavior hard to understand.&lt;/p&gt;

&lt;p&gt;What the developer needed was not just "clean code." They needed trustworthy signals they could use as evidence to answer harder questions: where the behavior lived, which paths shared it, what data was allowed to flow through it, and when their search was complete enough to make a safe change.&lt;/p&gt;

&lt;p&gt;In a situation like this, designing code that runs is not enough. The system also has to preserve enough evidence for the next developer to understand and change it safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good Design Generates Evidence
&lt;/h2&gt;

&lt;p&gt;The rendering bug exposed a deeper problem: the system did not make the notification path traceable enough to change safely. The developer needed to understand how a template became a rendered message before they could decide where a safe fix belonged.&lt;/p&gt;

&lt;p&gt;That happens a lot in software development. Often the hard part isn't the edit itself. It's understanding enough of the surrounding behavior to know where the change belongs and whether it is safe.&lt;/p&gt;

&lt;p&gt;To build that understanding, the developer looked for signals in the codebase that they could trust.&lt;/p&gt;

&lt;p&gt;A package name can give the developer a credible place to start. A class name can signal what kind of logic belongs inside. A shared enum can show which concepts are related. An explicit dependency can make side effects easier to see. A test can name behavior the system promises to preserve.&lt;/p&gt;

&lt;p&gt;But those signals are not automatically evidence. A package name can be a catch-all. A class can drift from its original purpose. A test name can describe one case while actually testing another.&lt;/p&gt;

&lt;p&gt;A signal becomes evidence when the code keeps the promise the signal makes. That's when the developer can reason from it.&lt;/p&gt;

&lt;p&gt;Good design doesn't just make code cleaner. It makes the system's signals trustworthy enough for the developer to reason about where behavior lives, what belongs together, which paths are unlikely to matter, and how far a change might travel.&lt;/p&gt;

&lt;p&gt;The signals a developer relies on change over the course of an investigation. Early on, they need help locating behavior and parsing logic. Later, they need help tracing consequences and deciding whether the remaining risk is small enough to stop searching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understandability Has Layers
&lt;/h2&gt;

&lt;p&gt;Developers do not move through these layers in a neat sequence. They may start by searching, open a file, realize it is the wrong place, trace a dependency, and search again. But the layers describe different kinds of signals the system provides as the developer builds enough understanding to decide what can be trusted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Perception: Can I Parse What I'm Looking At?
&lt;/h3&gt;

&lt;p&gt;Before the developer can reason about the notification leak, they have to parse the code in front of them. Shape, grouping, indentation, spacing, and hierarchy show them what belongs together and where one idea ends.&lt;/p&gt;

&lt;p&gt;If the rendering code is a dense block of conditionals, placeholder substitutions, and channel-specific branches, the developer has to reconstruct its shape before they can investigate the leak. They are spending attention on parsing before they can spend it on analysis.&lt;/p&gt;

&lt;p&gt;When this layer fails, the developer spends cognitive effort reconstructing the shape of the code before they can reason about where the logic might be wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Reasoning: Can I Hold the Relevant Ideas in Mind?
&lt;/h3&gt;

&lt;p&gt;Local reasoning is what happens after the developer has found a piece of code and starts asking whether they can understand what that unit is responsible for.&lt;/p&gt;

&lt;p&gt;A method named &lt;code&gt;renderTemplate&lt;/code&gt; sounds narrow. It makes a claim about what kind of reasoning belongs there.&lt;/p&gt;

&lt;p&gt;But if the method implements template loading, recipient lookup, tenant rules, placeholder resolution, channel formatting, missing-value behavior, and skip conditions inline, the body does not keep that promise. Each step may be necessary somewhere in the rendering workflow, but they don't belong at the same level of detail.&lt;/p&gt;

&lt;p&gt;Good boundaries reduce cognitive load because they tell the reader what kind of reasoning belongs inside them. Rendering, recipient lookup, tenant rules, channel formatting, and delivery decisions require &lt;a href="https://dev.to/christiecosky/why-the-single-responsibility-principle-protects-working-memory-3ioj"&gt;different mental frames&lt;/a&gt;. When one method mixes those frames at the implementation level, the reader has to hold too many mental models at once.&lt;/p&gt;

&lt;p&gt;When this layer fails, reading turns into mental juggling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation: Can I Find the Right Place to Start?
&lt;/h3&gt;

&lt;p&gt;In the notification bug, the developer could not start from "the rendering workflow" because template storage, placeholder mapping, data lookup, and channel-specific behavior were scattered across the system.&lt;/p&gt;

&lt;p&gt;A navigable system makes the first plausible place easier to find, and it makes unrelated places easier to rule out. A &lt;code&gt;notifications/templates&lt;/code&gt; package is a signal. It doesn't prove the leak is there, but it gives the developer a more credible starting point than &lt;code&gt;TenantController&lt;/code&gt;, &lt;code&gt;CustomerService&lt;/code&gt;, or &lt;code&gt;MessageUtils&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Good design doesn't just help developers &lt;a href="https://dev.to/christiecosky/mystery-meat-vs-breadcrumb-systems-4jie"&gt;find the right places to change&lt;/a&gt;; it also helps them rule out the wrong ones sooner.&lt;/p&gt;

&lt;p&gt;When this layer fails, too many places stay plausible for too long.&lt;/p&gt;

&lt;h3&gt;
  
  
  Propagation: Can I Trace Where This Change Can Travel?
&lt;/h3&gt;

&lt;p&gt;Once the developer finds one place where the wrong data enters a template, the next question is where that value can travel. Does the same placeholder appear in both notification channels? Does a fix in the placeholder renderer affect both channels, or only one of them?&lt;/p&gt;

&lt;p&gt;Good design makes propagation visible and bounded. If email and SMS both pass through the same placeholder renderer, the developer can see that a fix in the shared path may affect both channels. If they use separate channel-specific formatters, the developer can see that each path may need to be checked separately. The structure does not decide the fix for them, but it shows where the change can travel.&lt;/p&gt;

&lt;p&gt;Hidden listeners, framework hooks, database triggers, and distant side effects may be necessary, but they make the change path harder to see.&lt;/p&gt;

&lt;p&gt;When this layer fails, every edit carries an unknown blast radius.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stoppability: Can I Know When I've Seen Enough?
&lt;/h3&gt;

&lt;p&gt;Even after the developer understands where the data can travel, one question remains: have they checked enough to make the change safely?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/christiecosky/stoppability-in-code-design-2i4b"&gt;Stoppability&lt;/a&gt; is where enough trustworthy signals accumulate to justify stopping the search. Clear names, bounded responsibilities, visible dependencies, consistent patterns, and trustworthy tests combine into a confidence threshold. When those signals point in the same direction, stopping becomes justified. When they conflict, continued searching is rational.&lt;/p&gt;

&lt;p&gt;When this layer fails, small tasks become open-ended investigations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confidence Comes When Signals Agree
&lt;/h2&gt;

&lt;p&gt;One trustworthy signal rarely carries the whole investigation.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;notifications/templates&lt;/code&gt; package may suggest where template behavior belongs, but it is not enough by itself. The developer still needs to know whether placeholder names are defined consistently, whether template types share the same rendering path, whether email and SMS share the risky behavior, and whether the tests cover the cross-tenant rule.&lt;/p&gt;

&lt;p&gt;Confidence comes when those answers point in the same direction. When they do, the developer can start in the most plausible place, dismiss unrelated paths sooner, trace likely side effects, and stop when the remaining risk is small enough to act.&lt;/p&gt;

&lt;p&gt;When the signals conflict, confidence becomes harder to justify. The package suggests one home for the behavior, but a relevant-looking class sits somewhere else, and several template types only appear as magic strings outside the package. The test names claim to cover cross-tenancy, but the assertions prove something else. The system may still run, but the developer doesn't have a coherent interpretation they can trust.&lt;/p&gt;

&lt;p&gt;That is the difference between local readability and system understandability. Readable code is easier to parse. Understandable systems produce enough trustworthy evidence for developers to make justified decisions under uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understandability Depends on Trustworthy Signals
&lt;/h2&gt;

&lt;p&gt;Time is the stress test for all of these layers.&lt;/p&gt;

&lt;p&gt;The notification feature shipped six months earlier. The original author has moved on. The design tradeoffs and assumptions behind the implementation are no longer fresh in anyone's mind. The next developer has to reconstruct the behavior from the signals the system still provides.&lt;/p&gt;

&lt;p&gt;Some signals are weak from the start. Entity-based structures like &lt;code&gt;TenantController&lt;/code&gt; and &lt;code&gt;CustomerService&lt;/code&gt; may look organized, but they often give poor evidence about where feature behavior lives.&lt;/p&gt;

&lt;p&gt;Other signals lose trust over time. Formatting drift makes structure harder to recognize. Naming drift makes search less trustworthy. Boundaries weaken when classes absorb behavior their names no longer explain. Hidden dependencies make change paths harder to predict. One-off exceptions make it harder to know whether the behavior you found represents the rule or just another special case.&lt;/p&gt;

&lt;p&gt;The drift usually happens through changes that look harmless in isolation. One duplicated placeholder mapping. One special case. One class with a name broad enough to hold anything. Each one makes the system a little less trustworthy as a guide for the next developer.&lt;/p&gt;

&lt;p&gt;That is how "hard to follow" becomes operational risk. Sometimes the structure was misleading from the beginning. Sometimes it eroded slowly. Either way, the system doesn't provide reliable signals about its own behavior, and the next developer making a change is more likely to miss a path, misunderstand the logic, or introduce a bug.&lt;/p&gt;

&lt;p&gt;Understandability has to be preserved over time. When it's not, confidence gets harder to justify.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Raises the Stakes for Trustworthy Evidence
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//dev.to/christiecosky/why-ai-makes-readability-more-important-not-less-1lfi"&gt;AI-generated code can accelerate that drift.&lt;/a&gt; It can generate plausible signals very quickly, but plausible signals are not the same as trustworthy evidence. A generated class name can make a precise-sounding claim without preserving a real boundary. A generated test can name an important behavior without proving it.&lt;/p&gt;

&lt;p&gt;Reviewing AI-generated code means checking more than correctness. It means checking whether the names, boundaries, and tests are telling the truth.&lt;/p&gt;

&lt;p&gt;If AI adds a &lt;code&gt;TenantScopedTemplateRenderer&lt;/code&gt; and a test named &lt;code&gt;whenTenantDataCrossesBoundary_rejectsValue&lt;/code&gt;, those names make claims about where behavior lives and what behavior is protected. The reviewer has to decide whether the code actually keeps those promises. The class boundary may keep tenant-scoped rendering separate from recipient lookup and channel formatting, or it may only rename a workflow whose responsibilities are still mixed together. The test may prove the cross-tenant rendering rule, or it may be checking something else entirely.&lt;/p&gt;

&lt;p&gt;Code reviewers of AI-generated code should not ask only whether the new structure looks cleaner. They should ask whether it gives the next developer better evidence to reason from.&lt;/p&gt;

&lt;p&gt;If the names, boundaries, and tests preserve trustworthy evidence, AI can support understandability. If they only create the appearance of structure, humans still have more code to verify and reinterpret before they can trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Changes Code Review
&lt;/h2&gt;

&lt;p&gt;Understanding what makes a system understandable gives code reviewers better language. Instead of stopping at "This is hard to follow," they can point out which kind of evidence the design is failing to provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Perception:&lt;/strong&gt; Is the code shaped clearly enough to parse?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local reasoning:&lt;/strong&gt; Does each boundary contain one kind of reasoning?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation:&lt;/strong&gt; Can a future developer find where this behavior lives?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; Are the paths and side effects visible enough to trace?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stoppability:&lt;/strong&gt; Can a developer tell when they have checked enough?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal agreement:&lt;/strong&gt; Do the names, boundaries, dependencies, and tests point to the same interpretation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions turn a vague discomfort into something concrete. They do not guarantee a perfect design, but they give reviewers better ways to talk about risk before the risk turns into a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Systems That Remain Understandable
&lt;/h2&gt;

&lt;p&gt;The notification bug is one example of the larger design problem underneath this series.&lt;/p&gt;

&lt;p&gt;The path from template to placeholder to data source to recipient was not visible enough for the next developer to reason about quickly under pressure. When something went wrong, the system did not preserve enough evidence for the next person to trace what had happened.&lt;/p&gt;

&lt;p&gt;Formatting, naming, boundaries, cohesion, navigability, and stoppability can look like separate concerns, but they all contribute to the same larger goal. Each one either strengthens or weakens the system as a source of evidence for the next developer.&lt;/p&gt;

&lt;p&gt;Code has to execute correctly. Long-lived systems also have to preserve evidence for the people who will change them later, after memory has faded, authors have moved on, and the original context is gone.&lt;/p&gt;

&lt;p&gt;A system is sustainable when it can keep growing without erasing the evidence future developers need to change it safely.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI was my editor, but these ideas are my own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This article is part of a broader series exploring how code structure, navigability, and cohesion align with cognitive limits.&lt;/p&gt;

&lt;p&gt;If you're interested in the deeper dive, the full series is here:&lt;br&gt;
&lt;a href="https://christiecosky.com/series/designing-code-for-human-brains/" rel="noopener noreferrer"&gt;Designing Code for Human Brains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>maintainability</category>
      <category>readability</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Stoppability in Code Design</title>
      <dc:creator>Christie Cosky</dc:creator>
      <pubDate>Tue, 02 Jun 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/christiecosky/stoppability-in-code-design-2i4b</link>
      <guid>https://dev.to/christiecosky/stoppability-in-code-design-2i4b</guid>
      <description>&lt;h2&gt;
  
  
  The Small Change That Isn't
&lt;/h2&gt;

&lt;p&gt;You've been asked to add a new refund status: &lt;code&gt;NO_REFUND_REQUIRED&lt;/code&gt;. When it's set, no money should be returned to the customer. It sounds like a small change.&lt;/p&gt;

&lt;p&gt;You start where the status is set. You search for &lt;code&gt;Refund.setStatus()&lt;/code&gt; and find one call site. The value comes from the front end, and it's saved correctly. That part is straightforward.&lt;/p&gt;

&lt;p&gt;Then the next question shows up: &lt;em&gt;Where is this status actually used?&lt;/em&gt; You search for &lt;code&gt;Refund.getStatus()&lt;/code&gt;. Twelve results. Halfway through them, you find a place that returns money to the customer. You could add the condition there and call it done, but you hesitate.&lt;/p&gt;

&lt;p&gt;You've found &lt;em&gt;a&lt;/em&gt; place to change. The harder part is deciding whether it's the only place that matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Is this the only place refunds happen?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Is refund status interpreted somewhere else?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Will another workflow be affected?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Have I seen enough to stop?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The change looked easy. But you still don't know whether you've found everything that matters or what else this change might affect. You hesitate because stopping might be irresponsible.&lt;/p&gt;

&lt;p&gt;In many codebases, the hard part is not writing the change. It's knowing when you understand enough to make it.&lt;/p&gt;

&lt;p&gt;That moment of hesitation points to a quality of software design that doesn't get named very often: &lt;strong&gt;stoppability&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Stoppability Is
&lt;/h2&gt;

&lt;p&gt;Even after you've found the likely place and understood the code there, one question often remains:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have I seen enough to stop searching?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Stoppability is the degree to which a system helps developers reach justified confidence that they have seen enough to make a change with acceptable risk.&lt;/p&gt;

&lt;p&gt;This isn't certainty. It means the system gives enough evidence that more searching is unlikely to uncover anything that would materially change the work. A developer with enough confidence can stop because the remaining risk looks acceptable, not because all risk has been eliminated.&lt;/p&gt;

&lt;p&gt;That threshold depends on the task. Changing a label in the UI needs less evidence than modifying a refund workflow, changing security logic, or introducing new behavior in a critical path. Higher-risk changes require stronger evidence before stopping becomes rational.&lt;/p&gt;

&lt;h3&gt;
  
  
  Readability Still Matters
&lt;/h3&gt;

&lt;p&gt;Stoppability assumes developers can understand the code they've already found. If the local code is confusing, they may keep searching because they don't trust their interpretation.  (That local readability problem is explored in &lt;a href=""&gt;Readability is a Performance Constraint&lt;/a&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Systems Are Uneven
&lt;/h3&gt;

&lt;p&gt;Stoppability is local, not global. Most systems are uneven. Some areas are strongly stoppable: boundaries are clear, behavior is localized, and side effects are visible. Other areas are weakly stoppable: logic is scattered, naming is vague, and changes may spread in unclear ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unavoidable vs. Extraneous Exploration
&lt;/h3&gt;

&lt;p&gt;Some exploration is unavoidable. Real domains contain genuine complexity, and some architectures increase exploration as a tradeoff of their design. Distributed systems and event-driven workflows often require broader investigation.&lt;/p&gt;

&lt;p&gt;We can't eliminate exploration, but we can reduce extraneous exploration. Extraneous exploration comes from design choices that add uncertainty beyond what the domain requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;weak naming&lt;/li&gt;
&lt;li&gt;inconsistent structure&lt;/li&gt;
&lt;li&gt;scattered logic&lt;/li&gt;
&lt;li&gt;hidden side effects&lt;/li&gt;
&lt;li&gt;broken boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because those costs come from structure, not architecture or domain complexity, they can often be reduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Search Continues
&lt;/h2&gt;

&lt;p&gt;Search often continues even after the likely place to change is found because two uncertainties are still unresolved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uncertainty #1: Where Else Is This Behavior?
&lt;/h3&gt;

&lt;p&gt;After finding what looks like the right place to make a change, developers still need to answer:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have I found all the relevant locations, or could important logic exist somewhere else?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Stopping becomes rational when other locations are no longer plausible enough to justify more searching.&lt;/p&gt;

&lt;p&gt;Signals that strengthen location evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feature-oriented boundaries&lt;/li&gt;
&lt;li&gt;descriptive names&lt;/li&gt;
&lt;li&gt;consistent structure&lt;/li&gt;
&lt;li&gt;shared vocabulary&lt;/li&gt;
&lt;li&gt;clear ownership of behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These signals help in two ways: they make likely locations easier to find, and they make unlikely locations easier to dismiss. When those signals are missing, many other places remain plausible candidates, so search continues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uncertainty #2: What Else Will This Affect?
&lt;/h3&gt;

&lt;p&gt;Finding the right place isn't always enough. Developers also need to answer:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What else could this change affect?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A developer may know exactly where to edit the code, yet still keep searching because the consequences of the change are unclear.&lt;/p&gt;

&lt;p&gt;Stopping becomes rational when likely effects are visible, bounded, and understandable.&lt;/p&gt;

&lt;p&gt;Signals that strengthen propagation evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visible dependencies&lt;/li&gt;
&lt;li&gt;explicit workflows&lt;/li&gt;
&lt;li&gt;bounded side effects&lt;/li&gt;
&lt;li&gt;clear downstream paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These signals make it easier to see what might break before you change anything. When they are absent, more investigation is often justified.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Location and Propagation Combine
&lt;/h2&gt;

&lt;p&gt;Most systems contain a mix of strongly stoppable and weakly stoppable areas. These two uncertainties create four common working experiences.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Location Evidence&lt;/th&gt;
&lt;th&gt;Propagation Evidence&lt;/th&gt;
&lt;th&gt;Developer Experience&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;I'm still searching, and I still don't understand the risk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;I know where to edit, but I don't know what I'll break.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;I understand the likely impact, but I'm not sure I found all the places that matter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;I can move quickly with enough confidence.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Example: Two Refund Systems
&lt;/h2&gt;

&lt;p&gt;Let's go back to the &lt;code&gt;NO_REFUND_REQUIRED&lt;/code&gt; example and compare the experience in two systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  A System That Keeps You Looking
&lt;/h3&gt;

&lt;p&gt;You search for a &lt;code&gt;RefundStatus&lt;/code&gt; enum and can't find one. You start searching for individual refund statuses and find a mix of magic strings and individual constants in classes like &lt;code&gt;CustomerService&lt;/code&gt;, &lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;NotificationService&lt;/code&gt;, and others. Each class has some refund-related logic in it. Some overlaps; some is unique.&lt;/p&gt;

&lt;p&gt;Now the task is no longer just changing behavior. You're reconstructing the refund system from a pile of search results.&lt;/p&gt;

&lt;p&gt;You find one place that returns money to the customer and add your condition there, but stopping still feels premature. The system has not given enough evidence that you found every relevant location or that the change won't create additional effects somewhere else. Continued search still makes sense.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Search Continues
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Location:&lt;/strong&gt; Refund behavior has no clear home, so many places remain plausible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; Dependencies and downstream effects are unclear, so impact is hard to predict.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A System That Lets You Stop
&lt;/h3&gt;

&lt;p&gt;You find a &lt;code&gt;RefundStatus&lt;/code&gt; enum and add your new &lt;code&gt;NO_REFUND_REQUIRED&lt;/code&gt; value to it. You search for references to &lt;code&gt;RefundStatus&lt;/code&gt; and see most of them inside this package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;refunds&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="nc"&gt;RefundOrchestrator&lt;/span&gt;
  &lt;span class="nc"&gt;RefundPolicy&lt;/span&gt;
  &lt;span class="nc"&gt;RefundStatus&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package structure, shared enum references, and small number of outlying search results are evidence that refund behavior is mostly concentrated rather than scattered across the system.&lt;/p&gt;

&lt;p&gt;You quickly check the references outside that package to understand the likely downstream effects. One or two related updates are needed, but the available evidence suggests the consequences are limited and understandable.&lt;/p&gt;

&lt;p&gt;You open &lt;code&gt;RefundPolicy&lt;/code&gt;. There are many rules, but the code is readable. You find where money is returned to the customer and add your conditional check there.&lt;/p&gt;

&lt;p&gt;You haven't searched the entire codebase. You stop because the available evidence suggests more searching is unlikely to change your implementation. Stopping is rational.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Search Stops
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Location:&lt;/strong&gt; Refund behavior has a believable home, and references reveal the additional places likely to matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; Likely effects are visible, bounded, and easier to reason about.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Becomes Expensive
&lt;/h2&gt;

&lt;p&gt;Weakly stoppable systems impose a hidden cost before a change is finished: developers spend extra time and mental effort resolving uncertainty. That cost shows up as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;small changes turning into long investigations&lt;/li&gt;
&lt;li&gt;reading and discarding many irrelevant files&lt;/li&gt;
&lt;li&gt;tracing possible side effects across the codebase&lt;/li&gt;
&lt;li&gt;slower code reviews because completeness is unclear&lt;/li&gt;
&lt;li&gt;more interruptions to ask experts for confirmation&lt;/li&gt;
&lt;li&gt;extra manual testing to compensate for low confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repeated across a team, these costs increase cycle time.&lt;/p&gt;

&lt;p&gt;Weak stoppability also increases the risk of bugs. Developers may stop too early and miss relevant behavior, or work with incomplete understanding because they didn't realize they needed to search further.&lt;/p&gt;

&lt;p&gt;Over time, these areas become places people avoid. Knowledge concentrates in the few developers willing to deal with the uncertainty, and those people become bottlenecks.&lt;/p&gt;

&lt;p&gt;Strongly stoppable systems reverse that dynamic. Developers can make progress with justified confidence, ownership spreads, and changes get finished faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stoppability as a Design Goal
&lt;/h2&gt;

&lt;p&gt;Stoppability is about giving developers enough evidence to stop searching rationally. It is not about guaranteeing certainty. Software systems are rarely that clean, and experienced developers know there is always a chance that relevant behavior exists somewhere unexpected.&lt;/p&gt;

&lt;p&gt;In a well-designed system, finding one answer changes what you believe about that part of the system. A clear boundary, a trustworthy naming pattern, a consistent architectural rule, or an obvious ownership model lowers the probability that important behavior is hiding elsewhere. Search becomes narrower, cheaper, and eventually unnecessary.&lt;/p&gt;

&lt;p&gt;In a poorly designed system, the opposite happens. Every answer creates new plausible places to check. Logic is scattered, patterns are inconsistent, and boundaries can't be trusted. Developers keep reading, not because they're perfectionistic, but because that part of the system has not earned their confidence.&lt;/p&gt;

&lt;p&gt;This matters because most of software development isn't writing code. It is locating behavior, understanding consequences, reviewing changes, debugging problems, and deciding when enough investigation has been done to move forward. Systems that never let people stop turn routine work into chronic cognitive overhead.&lt;/p&gt;

&lt;p&gt;Good design does more than organize code. It changes what developers can rule out. It gives them enough confidence that they have seen enough to act under uncertainty. That is what makes a growing system sustainable.&lt;/p&gt;

&lt;p&gt;In my final post, Understandable Systems Generate Evidence, I'll pull these ideas together and look at how understandable systems preserve the evidence developers need to find behavior, trace consequences, and change code with justified confidence.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI was my editor, but these ideas are my own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This article is part of a broader series exploring how code structure, navigability, and cohesion align with cognitive limits.&lt;/p&gt;

&lt;p&gt;If you're interested in the deeper dive, the full series is here:&lt;br&gt;
&lt;a href="https://christiecosky.com/series/designing-code-for-human-brains/" rel="noopener noreferrer"&gt;Designing Code for Human Brains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>maintainability</category>
      <category>readability</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Why Some Codebases Are Hard to Understand: Cognitive Surface Area and the Hidden Cost of System Navigation</title>
      <dc:creator>Christie Cosky</dc:creator>
      <pubDate>Tue, 26 May 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/christiecosky/why-some-codebases-are-hard-to-understand-cognitive-surface-area-and-the-hidden-cost-of-system-4pkp</link>
      <guid>https://dev.to/christiecosky/why-some-codebases-are-hard-to-understand-cognitive-surface-area-and-the-hidden-cost-of-system-4pkp</guid>
      <description>&lt;h2&gt;
  
  
  Information Foraging: How Developers Navigate Systems
&lt;/h2&gt;

&lt;p&gt;Developers rarely read code from top to bottom. They navigate it. When debugging or extending a system, developers follow cues that suggest where relevant behavior might live, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Directory structure and package names&lt;/li&gt;
&lt;li&gt;Class names&lt;/li&gt;
&lt;li&gt;Method names&lt;/li&gt;
&lt;li&gt;Enums and constant values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each cue helps answer a constant question during exploration: "Where should I look next?"&lt;/p&gt;

&lt;p&gt;This behavior resembles patterns studied in human-computer interaction research. Information Foraging Theory describes how people search for information by following cues that signal where valuable information is likely to be found.&lt;/p&gt;

&lt;p&gt;When the cues are strong, developers can quickly eliminate large portions of the system and focus only on relevant areas. When they are weak, developers must explore much more of the system before they can understand the behavior.&lt;/p&gt;

&lt;p&gt;Software systems differ dramatically in how much of the codebase developers must explore to understand behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cognitive surface area describes how much of a system developers must mentally explore to understand or change behavior.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine debugging a refund bug: customers who should receive refunds are not getting them. &lt;/p&gt;

&lt;p&gt;You notice a &lt;code&gt;refunds&lt;/code&gt; package in the service layer. Inside it is a class named &lt;code&gt;RefundPolicy&lt;/code&gt;. You open it. The class contains a single public method and a few helpers, all focused on refund eligibility. Within a few seconds you know you're looking in the right place. Most of the system has already been ruled out. You only had to explore a small portion of the codebase.&lt;/p&gt;

&lt;p&gt;Now imagine the same bug in a system with weaker cues.&lt;/p&gt;

&lt;p&gt;Packages reveal only architectural layers (&lt;code&gt;controller&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;repository&lt;/code&gt;) but not functionality. No classes contain "refund" in their names. A full-text search returns seventeen results scattered across &lt;code&gt;CustomerService&lt;/code&gt;, &lt;code&gt;EmailService&lt;/code&gt;, &lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;PaymentService&lt;/code&gt;, and &lt;code&gt;ScheduledJobs&lt;/code&gt;. Each class is large, and refund-related logic appears in several different places.  Instead of quickly eliminating irrelevant areas, you must open and read multiple files just to determine whether they are related to the bug. Much more of the system must be explored before you know you're finally in the right place.&lt;/p&gt;

&lt;p&gt;In the first system, the structure provides clear breadcrumbs that guide exploration. In the second, the cues are weak and the developer must wander through the codebase. Breadcrumb systems create strong cues that guide exploration. In mystery meat systems, developers are forced to wander. &lt;em&gt;(I explored this distinction in more detail in &lt;a href="https://dev.to/christiecosky/mystery-meat-vs-breadcrumb-systems-4jie"&gt;Mystery Meat vs. Breadcrumb Systems&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Space: Why Cognitive Surface Area Matters
&lt;/h2&gt;

&lt;p&gt;When debugging or extending code, developers search the system for where behavior lives or should live. For example, when tracking down a bug:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is it in the front end or the back end?&lt;/li&gt;
&lt;li&gt;Which package is it in?&lt;/li&gt;
&lt;li&gt;Which class is it in?&lt;/li&gt;
&lt;li&gt;Which method is it in?&lt;/li&gt;
&lt;li&gt;What state triggered the behavior?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each question defines part of the search space a developer must explore. The more places a bug could plausibly live, the harder it becomes to narrow the investigation. Good structure narrows it by helping developers rule out large portions of the system quickly.&lt;/p&gt;

&lt;p&gt;Large systems aren't necessarily difficult to understand. Systems with large search spaces are. Some systems remain navigable even as they grow, while others become confusing surprisingly quickly.&lt;/p&gt;

&lt;p&gt;The difference often isn't size, but cognitive surface area.&lt;/p&gt;

&lt;p&gt;When a system has a small cognitive surface area, the path from a question ("Why is this refund failing?") to the responsible code is short. When surface area is large, developers must search much more of the system before they can find the relevant behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question: "Why did this refund fail?"

Small Surface Area
──────────────────
service/
  refund/
    RefundPolicy

Large Surface Area
──────────────────
service/
  CustomerService
  EmailService
  OrderService
  PaymentService
  ScheduledJobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've reviewed files only a hundred lines long that were surprisingly difficult to follow because understanding them required exploring several unrelated concerns. But I've also reviewed files twice that size that were much easier to understand because the structure was predictable and the naming was clear.&lt;/p&gt;

&lt;p&gt;The same contrast appears at the system level. I've worked in legacy codebases that were decades old and relatively easy to navigate, and in younger codebases that were much harder. The difference wasn't age or scale. It was how much of the system I had to mentally explore to get my work done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Sources of Cognitive Surface Area
&lt;/h2&gt;

&lt;p&gt;When developers investigate behavior in a system, they must find answers to several questions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Where should I look?&lt;/li&gt;
&lt;li&gt;What concepts do I need to understand?&lt;/li&gt;
&lt;li&gt;What patterns does this system follow?&lt;/li&gt;
&lt;li&gt;What other components influence this behavior?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These questions correspond to four different ways systems expand the amount of code developers must explore.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Developer Question&lt;/th&gt;
&lt;th&gt;Surface Area&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where should I look?&lt;/td&gt;
&lt;td&gt;Navigation surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What concepts do I need to understand?&lt;/td&gt;
&lt;td&gt;Concept surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What patterns does this system follow?&lt;/td&gt;
&lt;td&gt;Structural surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What other components influence this behavior?&lt;/td&gt;
&lt;td&gt;Dependency surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These four dimensions describe common sources of cognitive surface area within the code itself.&lt;/p&gt;

&lt;p&gt;Navigation surface area concerns &lt;em&gt;how many places behavior might live&lt;/em&gt;. Structural surface area concerns &lt;em&gt;whether developers can predict where behavior should live&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The dimensions are not entirely independent. Many problems increase several types of surface area at once. The goal of the framework is not perfect categorization, but to highlight the different ways systems become harder to understand.&lt;/p&gt;

&lt;p&gt;Each surface area expands for different reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigation surface area grows when behavior could plausibly live in many places.&lt;/li&gt;
&lt;li&gt;Concept surface area grows when multiple ideas must be understood at once.&lt;/li&gt;
&lt;li&gt;Structural surface area grows when the system's organization is unpredictable.&lt;/li&gt;
&lt;li&gt;Dependency surface area grows when many components influence the behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When teams say a codebase is "hard to understand," it often means one or more of these surface areas has grown too large.&lt;/p&gt;

&lt;p&gt;Some systems naturally have large search spaces because the domain itself is complex. Good architecture can't eliminate that complexity, but it can prevent unnecessary cognitive surface area from accumulating.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation Surface Area
&lt;/h3&gt;

&lt;p&gt;Navigation surface area relates to the question: &lt;strong&gt;Where should I look?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It describes how many places a developer might need to search to locate the behavior they are investigating. &lt;/p&gt;

&lt;p&gt;When navigation surface area is large, developers must explore many parts of the system before finding the relevant code. When it is small, much of the system can be ruled out immediately.&lt;/p&gt;

&lt;h4&gt;
  
  
  Layer-Based Package Dumping Grounds
&lt;/h4&gt;

&lt;p&gt;When packages are organized solely by architectural layer (&lt;code&gt;controller&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;repository&lt;/code&gt;), package names provide little guidance about where behavior lives. A developer can't rule out files by package and must search across many classes to locate the relevant functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="nc"&gt;CustomerService&lt;/span&gt;
  &lt;span class="nc"&gt;EmailService&lt;/span&gt;
  &lt;span class="nc"&gt;OrderService&lt;/span&gt;
  &lt;span class="nc"&gt;PaymentService&lt;/span&gt;
  &lt;span class="nc"&gt;ScheduledJobs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer trying to locate refund logic must open multiple classes to determine where it lives. The navigation surface area is large because many files remain plausible candidates.&lt;/p&gt;

&lt;p&gt;Compare that to functionality-oriented packaging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="n"&gt;refund&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; 
    &lt;span class="nc"&gt;RefundOrchestrator&lt;/span&gt;
    &lt;span class="nc"&gt;RefundPolicy&lt;/span&gt; 
    &lt;span class="nc"&gt;RefundProcessor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the search space is much smaller. Developers can navigate directly to the part of the system that matches the functionality they are investigating.&lt;/p&gt;

&lt;h4&gt;
  
  
  Entity God Classes
&lt;/h4&gt;

&lt;p&gt;Some systems organize controller, service, and repository classes around persistence entities. Over time, these classes accumulate many unrelated behaviors associated with that table.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
  &lt;span class="n"&gt;calculateLoyaltyDiscount&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt; 
  &lt;span class="n"&gt;processRefund&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt; 
  &lt;span class="n"&gt;sendPromotionalEmail&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because many unrelated behaviors accumulate in the same class, developers must search through large files to locate the behavior they need.&lt;/p&gt;

&lt;p&gt;Compare that with classes organized around specific behaviors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;LoyaltyDiscountCalculator&lt;/span&gt;
&lt;span class="nc"&gt;RefundPolicy&lt;/span&gt; 
&lt;span class="nc"&gt;PromotionalEmailSender&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the class name itself helps narrow the search space.&lt;/p&gt;

&lt;h4&gt;
  
  
  Microservices
&lt;/h4&gt;

&lt;p&gt;In distributed systems, navigation begins at the microservice level. Before debugging can begin, a developer must determine which service owns the behavior. Each additional &lt;em&gt;plausible&lt;/em&gt; service expands the navigation surface area. If service boundaries are unclear, developers may need to explore several services before locating the relevant behavior.&lt;/p&gt;

&lt;p&gt;When service boundaries align clearly with business capabilities, the search space is small. When they don't, the navigation surface area grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concept Surface Area
&lt;/h3&gt;

&lt;p&gt;Concept surface area relates to the question: &lt;strong&gt;What concepts must I understand?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It describes how many concepts a developer must hold in mind simultaneously while reading code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mixed Responsibilities
&lt;/h4&gt;

&lt;p&gt;Classes with multiple responsibilities force developers to switch between conceptual frames while reading the code.&lt;/p&gt;

&lt;p&gt;The problem is not that a piece of code performs several steps. Many methods legitimately coordinate a workflow. For example, a method that executes a refund process may validate the request, calculate the refund amount, call a payment gateway, and record the result. Those steps all belong to the same conceptual frame: executing the refund workflow.&lt;/p&gt;

&lt;p&gt;Mixed responsibilities appear when a boundary begins to absorb several different kinds of reasoning that do not belong to a single conceptual frame.&lt;/p&gt;

&lt;p&gt;For example, a single class or method might contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;business rules and policy decisions&lt;/li&gt;
&lt;li&gt;financial calculations&lt;/li&gt;
&lt;li&gt;persistence logic&lt;/li&gt;
&lt;li&gt;external API integration details&lt;/li&gt;
&lt;li&gt;notification behavior&lt;/li&gt;
&lt;li&gt;analytics or auditing decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if each individual piece of logic is understandable, the reader must constantly switch between different kinds of thinking while reading the code. Instead of reasoning about one idea at a time, the developer must juggle several unrelated concerns simultaneously.&lt;/p&gt;

&lt;p&gt;When responsibilities are separated into clearer conceptual units, each piece of code can usually be understood within a single mental frame. This reduces the number of ideas developers must keep in working memory at once and makes the system easier to reason about. &lt;em&gt;(This is how &lt;a href="https://dev.to/christiecosky/why-the-single-responsibility-principle-protects-working-memory-3ioj"&gt;the Single Responsibility Principle protects working memory&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Unclear Concept Boundaries
&lt;/h4&gt;

&lt;p&gt;Concept surface area also increases when a single concept is scattered across multiple components instead of having a clear home.&lt;/p&gt;

&lt;p&gt;For example, refund eligibility rules might appear in several places:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// OrderService &lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isExpired&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RefundNotAllowedException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PaymentService &lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAmount&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MAX_REFUND_AMOUNT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RefundNotAllowedException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CustomerService &lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isFlaggedForFraud&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RefundNotAllowedException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer trying to understand refund rules must inspect multiple classes and mentally assemble the full policy from scattered pieces.&lt;/p&gt;

&lt;p&gt;Compare that with a centralized design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;RefundPolicy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the concept has a clear boundary. Developers know exactly where to look to understand the rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structural Surface Area
&lt;/h3&gt;

&lt;p&gt;Structural surface area relates to the question: &lt;strong&gt;What patterns does this system follow?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It describes how predictable the organization of the system is.&lt;/p&gt;

&lt;p&gt;In predictable systems, developers can make strong assumptions about where certain types of logic belong. In unpredictable systems, those assumptions break down, forcing developers to inspect more of the codebase to confirm where behavior lives.&lt;/p&gt;

&lt;p&gt;Structural surface area grows when developers can't rely on consistent structural rules about where certain kinds of logic belong.&lt;/p&gt;

&lt;h4&gt;
  
  
  Inconsistent Structural Patterns
&lt;/h4&gt;

&lt;p&gt;Structural surface area increases when similar problems are solved using different structural patterns.&lt;/p&gt;

&lt;p&gt;For example, validation might sometimes appear in a &lt;code&gt;*Validator&lt;/code&gt; class, but other times be embedded directly inside a &lt;code&gt;*Service&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pattern A &lt;/span&gt;
&lt;span class="nc"&gt;CustomerValidator&lt;/span&gt; 

&lt;span class="c1"&gt;// Pattern B &lt;/span&gt;
&lt;span class="nc"&gt;CustomerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validateCustomer&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now developers cannot rely on a single structural rule to locate validation logic. They must remember that validation may appear in multiple structural forms.&lt;/p&gt;

&lt;p&gt;In systems with consistent patterns, developers quickly learn where different kinds of logic belong.&lt;/p&gt;

&lt;h4&gt;
  
  
  Inconsistent Naming Patterns
&lt;/h4&gt;

&lt;p&gt;Predictable naming patterns reinforce structural expectations, because they act as structural cues developers rely on to predict where logic belongs.&lt;/p&gt;

&lt;p&gt;Consider the following class names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CustomerService&lt;/span&gt; 
&lt;span class="nc"&gt;CustomerManager&lt;/span&gt; 
&lt;span class="nc"&gt;CustomerHelper&lt;/span&gt; 
&lt;span class="nc"&gt;CustomerUtils&lt;/span&gt; 
&lt;span class="nc"&gt;CustomerProcessor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The roles of these classes are unclear, making it difficult to infer what logic belongs in each one.&lt;/p&gt;

&lt;p&gt;Compare that with names that constrain responsibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CustomerCreditLimitValidator&lt;/span&gt; 
&lt;span class="nc"&gt;CustomerRefundEligibilityPolicy&lt;/span&gt; 
&lt;span class="nc"&gt;CustomerLoyaltyPointsCalculator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These names create structural expectations about what logic belongs in each class. For example, &lt;code&gt;CustomerCreditLimitValidator&lt;/code&gt; clearly suggests that only credit limit validation belongs there.&lt;/p&gt;

&lt;p&gt;Clear naming reduces structural surface area because developers can predict where logic should exist.&lt;/p&gt;

&lt;h4&gt;
  
  
  Structural Drift
&lt;/h4&gt;

&lt;p&gt;Structural surface area can also grow gradually as teams introduce exceptions to previously clear patterns.&lt;/p&gt;

&lt;p&gt;Imagine a system with a clear place for refund eligibility rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;RefundEligibilityPolicy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, a special case appears in unrelated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCustomerType&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;VIP&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// vips always get refunds&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then another rule is implemented separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;VipRefundPolicy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now refund eligibility logic exists in multiple structural forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a central policy class&lt;/li&gt;
&lt;li&gt;inline conditional logic&lt;/li&gt;
&lt;li&gt;a special-case policy class&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers can no longer infer where new rules should live because the structural pattern has broken down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Surface Area
&lt;/h3&gt;

&lt;p&gt;Dependency surface area relates to the question: &lt;strong&gt;What other components influence this behavior?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It describes how many other parts of the system must be understood in order to reason about the code being examined.&lt;/p&gt;

&lt;p&gt;When behavior depends on many other components, developers must explore those dependencies before they can confidently understand or modify the code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Distributed Workflow Chains
&lt;/h4&gt;

&lt;p&gt;Dependency surface area increases when behavior propagates through multi-step workflows across microservices.&lt;/p&gt;

&lt;p&gt;A developer might change this code in an order service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRiskCategory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculateRiskCategory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks local. But the risk category may later be read by a saga orchestrator, passed to a payment service, and then used by a fraud service to decide whether the transaction should be rejected.&lt;/p&gt;

&lt;p&gt;The dependency is not visible where the value is first written. To understand the impact of the change, the developer must trace how that data is consumed across several downstream steps.&lt;/p&gt;

&lt;p&gt;The more behavior depends on these distributed workflow chains, the larger the dependency surface area becomes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Implicit Side Effects
&lt;/h4&gt;

&lt;p&gt;Dependency surface area also increases when code triggers behavior that is not visible where the call occurs.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This appears to simply persist the order. But it might also trigger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inventory updates&lt;/li&gt;
&lt;li&gt;fraud checks&lt;/li&gt;
&lt;li&gt;event publication&lt;/li&gt;
&lt;li&gt;email notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these side effects occur through framework hooks, triggers, or event listeners, they may not be visible at the call site.&lt;/p&gt;

&lt;p&gt;Understanding the behavior requires knowledge of interactions that occur elsewhere in the system.&lt;/p&gt;

&lt;h4&gt;
  
  
  Event Chains
&lt;/h4&gt;

&lt;p&gt;Event-driven systems can expand dependency surface area. This trade-off is often intentional (event architectures provide flexibility and decoupling), but they also make system behavior harder to reason about.&lt;/p&gt;

&lt;p&gt;Publishing an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;eventBus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderPlacedEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;might trigger several consumers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;InventoryService&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EmailService&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BillingService&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AnalyticsService&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To understand the full consequences of placing an order, developers must track how the event propagates through the system.&lt;/p&gt;

&lt;p&gt;Long event chains make it harder to predict the downstream effects of a change.&lt;/p&gt;




&lt;p&gt;These dimensions describe four different ways systems increase the amount of code developers must explore to understand behavior.&lt;/p&gt;

&lt;p&gt;Cognitive surface area grows when developers must explore more of the system to understand behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mechanical Size vs Cognitive Surface Area
&lt;/h2&gt;

&lt;p&gt;We usually measure system size mechanically: lines of code, binary size, runtime performance. Those metrics say very little about how difficult a system is to understand. Two systems can contain the same number of lines of code but impose very different cognitive demands. What matters is how much of the system developers must explore to understand behavior.&lt;/p&gt;

&lt;p&gt;Human working memory is limited. When developers must load too many unrelated concepts at once, reasoning slows and mistakes become more likely.&lt;/p&gt;

&lt;p&gt;A large system can still have a small cognitive surface area if developers can quickly navigate to the code that contains the relevant concepts. When boundaries are clear and structure is predictable, developers only need to load the concepts relevant to the task at hand into their working memory.&lt;/p&gt;

&lt;p&gt;A large system with a &lt;strong&gt;small cognitive surface area&lt;/strong&gt; typically has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear boundaries between packages, classes, and methods&lt;/li&gt;
&lt;li&gt;Predictable structure&lt;/li&gt;
&lt;li&gt;Strong navigation cues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A large system with a &lt;strong&gt;large cognitive surface area&lt;/strong&gt; tends to have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scattered logic&lt;/li&gt;
&lt;li&gt;Inconsistent patterns and unpredictable structure&lt;/li&gt;
&lt;li&gt;Hidden dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional code complexity metrics focus on control flow inside individual functions — how difficult the code is to simulate mentally. Cognitive surface area focuses on how much of the system developers must explore to understand behavior — how difficult the system is to navigate and reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Architecture Shrinks Cognitive Surface Area
&lt;/h2&gt;

&lt;p&gt;Many well-known design principles in software engineering reduce one or more dimensions of cognitive surface area:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Practice&lt;/th&gt;
&lt;th&gt;Primary Surface Area Reduced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Domain-oriented package organization&lt;/td&gt;
&lt;td&gt;Navigation surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clear, descriptive naming&lt;/td&gt;
&lt;td&gt;Navigation surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single Responsibility Principle&lt;/td&gt;
&lt;td&gt;Concept surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small, focused methods&lt;/td&gt;
&lt;td&gt;Concept surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistent architectural patterns&lt;/td&gt;
&lt;td&gt;Structural surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predictable naming conventions (&lt;code&gt;*Validator&lt;/code&gt;, &lt;code&gt;*Calculator&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Structural surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loose coupling&lt;/td&gt;
&lt;td&gt;Dependency surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency injection&lt;/td&gt;
&lt;td&gt;Dependency surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Individually, these practices can feel like stylistic preferences or architectural taste. Collectively, they shape how easy a system is to explore and reason about. This is why large codebases can still feel easy to navigate, while small systems can feel overwhelming when their structure is unclear.&lt;/p&gt;

&lt;p&gt;Good architecture helps minimize how much of the system developers must explore to locate, understand, and reason about behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cognitive Surface Area Slows Teams Down
&lt;/h2&gt;

&lt;p&gt;Cognitive surface area directly affects engineering velocity because it determines how much of the system developers must explore before they can safely modify behavior.&lt;/p&gt;

&lt;p&gt;Large surface area means developers must explore more of the system before they can safely modify behavior. This slows debugging, pull-request review, onboarding, refactoring, and incident response.&lt;/p&gt;

&lt;p&gt;Because a large portion of engineering time is spent understanding existing code rather than writing new code, these exploration costs dominate day-to-day development work. These delays compound across the team: onboarding takes longer, pull requests require more context to review, and incidents take longer to diagnose. When developers must search more of the system, load more concepts into working memory, navigate inconsistent patterns, or reason about hidden dependencies, progress slows. &lt;em&gt;(I explored how readability constrains performance in &lt;a href="https://dev.to/christiecosky/readability-is-a-performance-constraint-15fa"&gt;Readability is a Performance Constraint&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reducing cognitive surface area shortens the path between seeing a problem and understanding the code responsible for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Is Expanding Cognitive Surface Area Faster
&lt;/h2&gt;

&lt;p&gt;Traditionally this growth happened slowly. AI changes that dynamic by dramatically reducing the cost of producing code. Systems can now grow faster than teams can control the cognitive surface area they create.&lt;/p&gt;

&lt;p&gt;AI-generated features introduce new classes, modules, and services rapidly, often based on local context rather than system-wide architectural patterns unless those patterns are explicitly reinforced. If the surrounding code already contains structural problems, like inconsistent patterns, overloaded abstractions, or unclear boundaries, AI will often reproduce them.&lt;/p&gt;

&lt;p&gt;Without guardrails, AI can unintentionally increase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;navigation surface area (more places code might live)&lt;/li&gt;
&lt;li&gt;concept surface area (more responsibilities mixed together)&lt;/li&gt;
&lt;li&gt;structural surface area (more inconsistent patterns)&lt;/li&gt;
&lt;li&gt;dependency surface area (more hidden interactions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is systems that grow quickly but become progressively harder to understand.&lt;/p&gt;

&lt;p&gt;AI makes it easier to produce code, but it does not automatically make systems easier to navigate. &lt;em&gt;(I previously explored how &lt;a href="https://dev.to/christiecosky/why-ai-makes-readability-more-important-not-less-1lfi"&gt;AI makes readability more important, not less&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnosing Cognitive Surface Area
&lt;/h2&gt;

&lt;p&gt;When a codebase feels difficult to understand, the difficulty usually traces back to one or more of these questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigation surface area:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do I know where the relevant behavior lives?&lt;/li&gt;
&lt;li&gt;Are there many plausible places to check?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Concept surface area:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many different ideas are mixed together in this code?&lt;/li&gt;
&lt;li&gt;Do I have to hold multiple responsibilities in mind at once?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Structural surface area:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the system follow predictable patterns?&lt;/li&gt;
&lt;li&gt;Can I guess where similar logic should exist?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dependency surface area:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What other components influence this behavior?&lt;/li&gt;
&lt;li&gt;Are those dependencies visible from the code in front of me?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When several of these questions are difficult to answer, developers must explore more of the system to understand what it is doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Cognitive Surface Area
&lt;/h2&gt;

&lt;p&gt;The cheapest way to manage cognitive surface area is to prevent it from growing in the first place. But most teams inherit systems that already contain large cognitive surface area. In those environments, improvement usually happens gradually.&lt;/p&gt;

&lt;p&gt;One approach we used on Taz was to clean as we went. We added a new base package to our monolith where all new code went.  Legacy code remained isolated in the old base package. As old features were extended or refactored, they were moved into the new base package. Over time, the amount of legacy code shrunk.&lt;/p&gt;

&lt;p&gt;Managing cognitive surface area often involves small improvements applied consistently:&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduce Navigation Surface Area
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Organize packages around functionality, not just architectural layers&lt;/li&gt;
&lt;li&gt;Break up packages that contain unrelated functionality&lt;/li&gt;
&lt;li&gt;Use clear, descriptive package and class names&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reduce Concept Surface Area
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Refactor multi-responsibility classes&lt;/li&gt;
&lt;li&gt;Apply the Single Responsibility Principle to new classes&lt;/li&gt;
&lt;li&gt;Use names that are fences, keeping related functionality inside and unrelated functionality outside&lt;/li&gt;
&lt;li&gt;Break large methods up using helper methods&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reduce Structural Surface Area
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Maintain consistent architectural patterns&lt;/li&gt;
&lt;li&gt;Use predictable naming conventions&lt;/li&gt;
&lt;li&gt;Correct structural drift when introducing new code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reduce Dependency Surface Area
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Limit hidden side effects&lt;/li&gt;
&lt;li&gt;Prefer explicit dependencies&lt;/li&gt;
&lt;li&gt;Limit dependencies across unrelated modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI can help with many of these tasks. Teams can encode rules into agent instructions or automated code reviews. AI can assist with refactoring, renaming, and decomposing large classes. Used carefully, AI can reduce cognitive surface area instead of expanding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal: Keep the System Navigable
&lt;/h2&gt;

&lt;p&gt;Software systems inevitably grow. But they don't have to become unnavigable as they grow. The goal of good architecture is not only to control complexity inside individual components, but also to keep the cognitive surface area of the system manageable over time.&lt;/p&gt;

&lt;p&gt;The real difficulty of a system isn't its raw complexity. It's how much of the system developers must explore before they understand it.&lt;/p&gt;

&lt;p&gt;Reducing cognitive surface area helps you find the code. It limits how much of the system you need to search and understand just to locate behavior.&lt;/p&gt;

&lt;p&gt;But finding the code is not the same as feeling confidence that it's safe to change. You can reach the right place in the system and still hesitate, still wondering what you might be missing.&lt;/p&gt;

&lt;p&gt;Navigation tells you where to go. It doesn't tell you whether you understand enough to make a safe change — and that's the gap I'll explore next in my next article, Stoppability in Code Design.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI was my editor, but these ideas are my own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This article is part of a broader series exploring how code structure, navigability, and cohesion align with cognitive limits.&lt;/p&gt;

&lt;p&gt;If you're interested in the deeper dive, the full series is here:&lt;br&gt;
&lt;a href="https://christiecosky.com/series/designing-systems-for-human-brains/" rel="noopener noreferrer"&gt;Designing Systems for Human Brains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>maintainabilty</category>
      <category>readability</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Why AI Makes Readability More Important, Not Less</title>
      <dc:creator>Christie Cosky</dc:creator>
      <pubDate>Tue, 19 May 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/christiecosky/why-ai-makes-readability-more-important-not-less-1lfi</link>
      <guid>https://dev.to/christiecosky/why-ai-makes-readability-more-important-not-less-1lfi</guid>
      <description>&lt;p&gt;Software development has always had an asymmetry: code can be written faster than it can be understood.&lt;/p&gt;

&lt;p&gt;Code is written once but &lt;a href="https://dev.to/christiecosky/readability-is-a-performance-constraint-15fa"&gt;read repeatedly&lt;/a&gt; during reviews, debugging sessions, refactors, and production incidents. The real constraint has always been understanding code rather than typing it.&lt;/p&gt;

&lt;p&gt;AI dramatically widens that asymmetry. Tools like GitHub Copilot and Claude Code can generate thousands of lines of code in minutes, but the speed at which humans understand code hasn't changed.&lt;/p&gt;

&lt;p&gt;Before AI, the speed of writing code naturally constrained how quickly systems could grow. AI removes much of that friction. But human comprehension speed hasn't changed. Systems can now grow faster than teams can understand them — or keep them coherent.&lt;/p&gt;

&lt;p&gt;The asymmetry isn't new, but the magnitude is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Systems Can Now Grow Faster Than Teams Can Understand
&lt;/h2&gt;

&lt;p&gt;Discussions of AI-generated code often focus on correctness or "AI slop." That risk is real. But a larger risk is structural: how quickly disorder can accumulate as systems grow faster than humans can maintain them.&lt;/p&gt;

&lt;p&gt;AI removes much of the friction from writing code. AI can generate large portions of features from a handful of prompts. But every line of code still has to be read, reviewed, debugged, refactored, and extended. Even correct code increases cognitive load, because it still increases the amount of code humans must understand.&lt;/p&gt;

&lt;p&gt;A messy system with 20k lines of code is annoying. A messy system with 500k AI-generated lines of code becomes extremely difficult to understand.&lt;/p&gt;

&lt;p&gt;AI increases code volume while increasing the speed at which structural entropy, like small inconsistencies and duplicated code blocks, can accumulate.&lt;/p&gt;

&lt;p&gt;As that entropy grows, so does the system's cognitive surface area: the amount of structure developers must mentally explore to understand the code.&lt;/p&gt;

&lt;p&gt;As systems grow faster, the team's mental model can struggle to keep up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottleneck Shifts
&lt;/h2&gt;

&lt;p&gt;When developers switch to using AI tools, they spend less time manually writing code and more time analyzing AI-generated code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reviewing it&lt;/li&gt;
&lt;li&gt;validating that it actually behaves correctly&lt;/li&gt;
&lt;li&gt;integrating it correctly into the system&lt;/li&gt;
&lt;li&gt;refactoring it into something readable&lt;/li&gt;
&lt;li&gt;aligning it with existing patterns in the codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottleneck shifts from typing code to understanding, reviewing, and shaping the generated code to fit the system.&lt;/p&gt;

&lt;p&gt;As code volume increases, code review becomes more cognitively demanding. Large AI-generated changes increase review fatigue, making it harder for both the author and the reviewer to fully understand and validate every change.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Introduces Structural Drift
&lt;/h2&gt;

&lt;p&gt;AI generates code from the local context it sees rather than a full understanding of the system's architecture. As a result, it does not reliably maintain global structural consistency across the system. Small inconsistencies can accumulate across many changes, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slightly different abstractions&lt;/li&gt;
&lt;li&gt;duplicate logic&lt;/li&gt;
&lt;li&gt;inconsistent naming&lt;/li&gt;
&lt;li&gt;subtle architectural drift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individually, these differences are small. But across hundreds of changes they compound.&lt;/p&gt;

&lt;p&gt;Over time, these inconsistencies increase the cognitive entropy — the amount of disorder a reader must make sense of — of the system. The structure becomes harder to recognize. The patterns become less predictable. When patterns become unpredictable, understanding slows down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Humans Maintain Coherence
&lt;/h2&gt;

&lt;p&gt;In addition to validating correctness, developers must ensure AI-generated code fits the system's existing structure and conventions. This maintains system coherence. By coherence, I mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The structure of the new code makes sense&lt;/li&gt;
&lt;li&gt;The patterns remain consistent internally and across the system&lt;/li&gt;
&lt;li&gt;The boundaries of methods, classes, and packages remain clear&lt;/li&gt;
&lt;li&gt;Responsibilities remain understandable&lt;/li&gt;
&lt;li&gt;Complexity remains manageable&lt;/li&gt;
&lt;li&gt;Naming remains accurate and meaningful&lt;/li&gt;
&lt;li&gt;Similar problems are solved in similar ways&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Readable code is required to make that possible.&lt;/p&gt;

&lt;p&gt;AI does not automatically maintain system coherence. As codebases grow faster through AI code generation, a larger part of the developer's job becomes counteracting structural drift. Without that effort, the system becomes harder to understand over time, even if every individual piece works.&lt;/p&gt;

&lt;p&gt;Readable structure allows coherence to survive as the system grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Can Also Help Maintain Readability
&lt;/h2&gt;

&lt;p&gt;AI can also help with the work of maintaining coherence. With enough context and a human in the loop to validate their proposals, frontier models are surprisingly good at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structural refactors&lt;/li&gt;
&lt;li&gt;naming improvements&lt;/li&gt;
&lt;li&gt;package reorganization&lt;/li&gt;
&lt;li&gt;large mechanical changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've had good results asking Claude Code to analyze an architectural layer of a codebase and propose an updated package and class structure based on the business functionality it detects — &lt;a href="https://dev.to/christiecosky/mystery-meat-vs-breadcrumb-systems-4jie"&gt;adding breadcrumbs for me&lt;/a&gt;. (More on using AI to improve a hard-to-navigate codebase in a future article.) It saves time by handling the analysis and tedious cut-and-paste, and the return on investment multiplies every time someone works in that part of the system and benefits from the improved navigability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint Hasn't Changed
&lt;/h2&gt;

&lt;p&gt;AI dramatically reduces the cost of producing code. But the fundamental constraint in software development has never been typing. It has always been understanding. Human comprehension speed does not scale with code generation speed. &lt;/p&gt;

&lt;p&gt;Whether a system remains understandable depends on its coherence: the consistency of its structure, boundaries, and patterns. Readable structure is what preserves that coherence as a system evolves.&lt;/p&gt;

&lt;p&gt;Some companies are experimenting with AI-driven development loops where agents generate specifications, write implementations, run tests, and even review their own changes. In those environments, humans may not read every line of code. But even highly automated systems still rely on humans when something goes wrong. Production incidents, unexpected behavior, and architectural changes require developers to understand what the system is doing and why.&lt;/p&gt;

&lt;p&gt;Whether code is written manually, with AI assistance, or through AI-driven development loops, understandable systems are still necessary for the humans maintaining them.&lt;/p&gt;

&lt;p&gt;But what actually determines how difficult a system is to understand?&lt;/p&gt;

&lt;p&gt;In my next article, I'll go into more depth about cognitive surface area: the property of a system that determines how much of it developers must mentally explore to understand or change behavior.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI was my editor, but these ideas are my own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This article is part of a broader series exploring how code structure, navigability, and cohesion align with cognitive limits.&lt;/p&gt;

&lt;p&gt;If you're interested in the deeper dive, the full series is here:&lt;br&gt;
&lt;a href="https://christiecosky.com/series/designing-systems-for-human-brains/" rel="noopener noreferrer"&gt;Designing Systems for Human Brains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>readability</category>
      <category>maintainability</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Why the Single Responsibility Principle Protects Working Memory</title>
      <dc:creator>Christie Cosky</dc:creator>
      <pubDate>Tue, 12 May 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/christiecosky/why-the-single-responsibility-principle-protects-working-memory-3ioj</link>
      <guid>https://dev.to/christiecosky/why-the-single-responsibility-principle-protects-working-memory-3ioj</guid>
      <description>&lt;p&gt;In my previous article, Mystery Meat vs. Breadcrumb Systems, I argued that organizing code by feature reduces unnecessary exploration. Feature-based packages make it easier to navigate to the right place in the system.&lt;/p&gt;

&lt;p&gt;But navigation alone doesn't produce comprehension. You can open &lt;code&gt;service.pricing&lt;/code&gt; immediately and still have no idea what you're looking at. Finding the file only solves the location problem. Understanding it requires something else.&lt;/p&gt;

&lt;p&gt;The Single Responsibility Principle is usually framed as a design rule: "a class should have one reason to change." But that framing doesn't explain why classes that follow it are often easier to understand.&lt;/p&gt;

&lt;p&gt;The SRP protects working memory by limiting the number of responsibilities within a boundary. Usually the SRP applies to the boundary of a class, but the same constraint can be applied to packages and methods.&lt;/p&gt;

&lt;p&gt;When a boundary contains one responsibility, understanding is easier. When it mixes responsibilities, reading turns into code simulation. Simulation is time consuming and adds cognitive overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of Cognitive Thrash
&lt;/h2&gt;

&lt;p&gt;Our working memory is limited. We can only hold a small number of conceptual frames in our heads at once.&lt;/p&gt;

&lt;p&gt;By "conceptual frame," I mean the mental model — the mode of reasoning — the reader uses to interpret what the code is doing. Validation logic is read differently than persistence logic. Calculation logic is read differently than logging.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;stable conceptual frame&lt;/strong&gt; is one in which the reader can predict the type of reasoning required without repeatedly shifting mental models.&lt;/p&gt;

&lt;p&gt;Each responsibility activates a conceptual frame. When a boundary contains multiple responsibilities, the reader must repeatedly switch frames. That context switching isn't free. The reader has to set aside one mental model, load another, and preserve the relationships between them.&lt;/p&gt;

&lt;p&gt;That repeated switching creates cognitive thrash.&lt;/p&gt;

&lt;p&gt;Cognitive load theory separates intrinsic load (the complexity of the problem) from extraneous load (the cost of how it's presented). The SRP doesn't reduce intrinsic complexity; pricing rules are still pricing rules. But it reduces extraneous load by limiting unnecessary frame switching inside a boundary.&lt;/p&gt;

&lt;p&gt;Code that follows the SRP reduces context switching by stabilizing the frame contained within each boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  SRP Across Every Boundary
&lt;/h2&gt;

&lt;p&gt;The SRP can apply to more than just the boundary created by a class definition. Any structural boundary in the system can contain (or mix) conceptual frames.&lt;/p&gt;

&lt;p&gt;At each level, the question is the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this boundary contain one stable conceptual frame, or does it mix several?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  System Level: Packages
&lt;/h3&gt;

&lt;p&gt;At the package level, we can group features together. Feature-based packages increase cohesion and enable navigation. They reduce the search space and make functionality visible in the structure of the system.&lt;/p&gt;

&lt;p&gt;When packages are organized primarily by framework layer or persistence entity, features get fragmented. Instead of navigating to a visible feature boundary, readers must search and infer relationships across layers.&lt;/p&gt;

&lt;p&gt;At the system level, this determines whether you can find functionality without relying on memory — yours, a teammate's, or the original author's (who left three years ago).&lt;/p&gt;

&lt;p&gt;A cohesive package structure stabilizes feature-level frames; a fragmented structure forces reconstruction and search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Class Level: Objects
&lt;/h3&gt;

&lt;p&gt;At the class level, SRP is commonly defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A class should only have one reason to change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cognitively, that means a class should represent one coherent responsibility which activates one stable conceptual frame.&lt;/p&gt;

&lt;p&gt;A class that validates, calculates, coordinates, persists, and logs mixes frames. Readers can't rely on the class name or structure to predict the pattern of logic inside. They must simulate execution, trace control flow, and hold multiple responsibilities in working memory.&lt;/p&gt;

&lt;p&gt;When simulation replaces pattern recognition, reading slows down. Every interaction requires reconstruction.&lt;/p&gt;

&lt;p&gt;Classes with a single responsibility contain one frame inside the boundary, so the reader knows what kind of logic to expect, as well as what does &lt;em&gt;not&lt;/em&gt; belong there.&lt;/p&gt;

&lt;p&gt;Good names reinforce that boundary. Class names with cohesive boundaries usually answer one or more of these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose:&lt;/strong&gt; Why does this class exist? (&lt;code&gt;InvoiceReconciliation*&lt;/code&gt;, &lt;code&gt;OrderFulfillment*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role:&lt;/strong&gt; What kind of logic is this? (&lt;code&gt;*Calculator&lt;/code&gt;, &lt;code&gt;*Validator&lt;/code&gt;, &lt;code&gt;*Orchestrator&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraint:&lt;/strong&gt; What variant is this? (&lt;code&gt;*Cached*&lt;/code&gt;, &lt;code&gt;*External*&lt;/code&gt;, &lt;code&gt;*ReadOnly*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mechanism:&lt;/strong&gt; How is it implemented? (&lt;code&gt;*Jdbc*&lt;/code&gt;, &lt;code&gt;*Stripe*&lt;/code&gt;, &lt;code&gt;*Rest*&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The role suffix often signals the one reason the class has to change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OrderTotalCalculator&lt;/code&gt; changes if calculations change.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CheckoutOrchestrator&lt;/code&gt; changes if sequencing changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RefundValidator&lt;/code&gt; changes if validation rules change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These repeatable naming patterns enable pattern recognition. Pattern recognition works best when frames are stable.&lt;/p&gt;

&lt;p&gt;Names don't create cohesion, but they do constrain what belongs inside the boundary. If a name can't meaningfully exclude logic, the class doesn't represent a single stable frame.&lt;/p&gt;

&lt;p&gt;This is why suffixes like &lt;code&gt;*Service&lt;/code&gt;, &lt;code&gt;*Helper&lt;/code&gt; or &lt;code&gt;*Manager&lt;/code&gt; tend to become junk drawers. They describe broad roles, not stable conceptual frames. Almost anything can fit inside them — and once it does, frame containment erodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method Level: Units of Thought
&lt;/h3&gt;

&lt;p&gt;The SRP can apply at the method level too.&lt;/p&gt;

&lt;p&gt;For most of my career, I used helper methods to prevent duplicate code or isolate especially complicated logic.&lt;/p&gt;

&lt;p&gt;Recently, I began using helper methods as &lt;strong&gt;labels for units of thought&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of writing a dense public method with comments preceding each section, I now extract helper methods as labeled steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;validateRequest&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;calculateTotals&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;applyDiscounts&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;persistOrder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public method becomes a narrative. Each call marks a structural boundary around one stable conceptual frame.&lt;/p&gt;

&lt;p&gt;Instead of holding the entire implementation in working memory, the reader processes one frame at a time.&lt;/p&gt;

&lt;p&gt;I don't do this with every public method; just the ones that benefit from it.&lt;/p&gt;

&lt;p&gt;A simple test:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the public method read like a clear sequence of steps after extraction?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If not, the extraction hasn't improved frame containment. Keep the logic inline.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Extraction Backfires
&lt;/h3&gt;

&lt;p&gt;Extraction reduces density but increases indirection.&lt;/p&gt;

&lt;p&gt;A boundary only helps if it contains a stable frame. When extraction forces the reader to jump across multiple files or through layers of trivial delegation, it replaces conceptual compression with navigational friction.&lt;/p&gt;

&lt;p&gt;We want to increase cohesion, not decrease it.&lt;/p&gt;

&lt;p&gt;Warning signs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Methods extracted that don't represent a meaningful step.&lt;/li&gt;
&lt;li&gt;One-line delegations that merely rename trivial logic.&lt;/li&gt;
&lt;li&gt;Control flow that requires navigating through several classes to understand a simple process.&lt;/li&gt;
&lt;li&gt;"Micro-objects" that fragment one coherent idea across multiple files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If extraction increases frame switching instead of reducing it, the boundary is no longer stabilizing the frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When Boundaries Degrade
&lt;/h2&gt;

&lt;p&gt;SRP violations don't just make a single class harder to read; over time, degraded boundaries affect the entire system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Increased Mental Simulation
&lt;/h3&gt;

&lt;p&gt;When a boundary contains multiple responsibilities, skimming stops being safe. Readers can't rely on the name or the structure of the class to predict what belongs inside or what pattern of logic they'll find. They must trace execution step-by-step, juggling multiple conceptual frames in working memory.&lt;/p&gt;

&lt;p&gt;When frames become unstable, recognition becomes unreliable and readers fall back to simulation. Recognition is fast. Simulation is slow. Over time, slow becomes normal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decreased Code Review Efficacy
&lt;/h3&gt;

&lt;p&gt;At a small scale, one unstable boundary is an annoyance. At a large scale, unstable boundaries create systemic fatigue.&lt;/p&gt;

&lt;p&gt;Reviewers spend more time reconstructing mental models before they can even evaluate correctness. Frame instability forces deeper inspection on every change, and eventually reviewers adapt by conserving effort. They skim when it isn't safe to skim.&lt;/p&gt;

&lt;p&gt;Bugs hide in the noise because unstable boundaries made full understanding too expensive.&lt;/p&gt;

&lt;p&gt;Cognitive thrash spreads beyond the original class and into the review process itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  God Class Gravity
&lt;/h3&gt;

&lt;p&gt;Classes with loosely named boundaries (&lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;UserService&lt;/code&gt;) attract unrelated behavior. Because the name doesn't constrain a single responsibility, almost anything fits. The class stops representing a stable conceptual frame and becomes an expanding collection of unrelated behaviors.&lt;/p&gt;

&lt;p&gt;Developers hesitate to introduce new cohesive classes because doing so feels like violating the team's conventions. Entropy wins.&lt;/p&gt;

&lt;p&gt;Boundaries between functionality blur because the "entity service" contains all logic for all features that touch that entity.&lt;/p&gt;

&lt;p&gt;Unit tests grow harder to write, read, and understand. They stop reinforcing a coherent frame and instead mirror the fragmentation of the production code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowledge Concentration
&lt;/h3&gt;

&lt;p&gt;When responsibility isn't visible in structure, knowledge concentrates in people instead of boundaries.&lt;/p&gt;

&lt;p&gt;Ownership narrows to the developer who wrote the feature. Others avoid touching or refactoring that code because it feels risky. The bus factor increases.&lt;/p&gt;

&lt;p&gt;Knowledge centralizes, and unreadable structure increases risk concentration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Systemic Entropy
&lt;/h3&gt;

&lt;p&gt;As unstable boundaries multiply, feature boundaries degrade.&lt;/p&gt;

&lt;p&gt;Packages become dumping grounds for everything in that architectural layer. Navigability decreases as the system grows. New functionality gets fragmented to fit the existing structure.&lt;/p&gt;

&lt;p&gt;The cost compounds, and even experienced developers struggle to onboard or safely extend the system because it's difficult to build a reliable mental model of where functionality lives.&lt;/p&gt;

&lt;p&gt;Stable class-level boundaries reinforce package-level boundaries. When cohesion degrades at the class level, navigability degrades globally.&lt;/p&gt;

&lt;p&gt;Systemic drag follows.&lt;/p&gt;

&lt;p&gt;In a read-heavy discipline like software development, drag is lost performance. Readability now limits performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  SRP Reinterpreted
&lt;/h2&gt;

&lt;p&gt;SRP is often defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A class should have one reason to change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cognitively, the rule could be defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A boundary should contain one stable conceptual frame.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Software development is constrained by how quickly a team can find, understand, and safely change code. Cohesive boundaries protect comprehension at every level: package, class, and method.&lt;/p&gt;

&lt;p&gt;The SRP reduces frame switching inside a boundary. That reduction compounds.&lt;/p&gt;

&lt;p&gt;SRP aligns with constraints imposed by human cognition rather than being merely a stylistic preference. And individual cognition does not scale with system size.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Write Velocity Outpaces Cognition
&lt;/h2&gt;

&lt;p&gt;The number of boundaries grows as AI quickly generates code. The only way that growth remains sustainable is if those boundaries preserve frame stability instead of fragmenting it.&lt;/p&gt;

&lt;p&gt;As code volume grows, developers spend more time reviewing and validating than writing. Effective code review relies heavily on pattern recognition. Pattern recognition depends on frame stability.&lt;/p&gt;

&lt;p&gt;If boundaries between frames are unstable, AI amplifies the problem, and it spreads faster than ever.&lt;/p&gt;

&lt;p&gt;When cohesion degrades, reading becomes simulation, simulation becomes cognitive thrash, thrash becomes fatigue, and fatigue becomes risk.&lt;/p&gt;

&lt;p&gt;Applying the SRP across packages, classes and methods can help protect one resource that doesn't scale: human cognition.&lt;/p&gt;

&lt;p&gt;Next time, I'll explore why AI makes readability more important, not less.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI was my editor, but these ideas are my own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This article is part of a broader series exploring how code structure, navigability, and cohesion align with cognitive limits.&lt;/p&gt;

&lt;p&gt;If you're interested in the deeper dive, the full series is here:&lt;br&gt;
&lt;a href="https://christiecosky.com/series/designing-systems-for-human-brains/" rel="noopener noreferrer"&gt;Designing Systems for Human Brains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>readability</category>
      <category>maintainability</category>
      <category>softwareengineering</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Mystery Meat vs. Breadcrumb Systems</title>
      <dc:creator>Christie Cosky</dc:creator>
      <pubDate>Tue, 31 Mar 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/christiecosky/mystery-meat-vs-breadcrumb-systems-4jie</link>
      <guid>https://dev.to/christiecosky/mystery-meat-vs-breadcrumb-systems-4jie</guid>
      <description>&lt;p&gt;It's your first week at a new job. You've checked out the repository and are scanning the package structure. The codebase is organized by &lt;code&gt;*Service&lt;/code&gt;, &lt;code&gt;*Controller&lt;/code&gt;, and &lt;code&gt;*Repository&lt;/code&gt;. Within each layer, files are grouped by persistence entity: &lt;code&gt;Order*&lt;/code&gt;, &lt;code&gt;User*&lt;/code&gt;, &lt;code&gt;Product*&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;You've been asked to fix a bug in the pricing logic. Must be in &lt;code&gt;OrderService&lt;/code&gt;, right?&lt;/p&gt;

&lt;p&gt;You open the file. It's 4,000 lines long. Some methods are hundreds of lines.&lt;/p&gt;

&lt;p&gt;You search for &lt;code&gt;pric*&lt;/code&gt;. Not found.&lt;/p&gt;

&lt;p&gt;You search the entire codebase. The word appears in seven files, scattered across variable names, constants, and magic strings. You open the first result and start reading. This doesn't seem right. You move to the next file.&lt;/p&gt;

&lt;p&gt;You can feel the cognitive load accumulating.&lt;/p&gt;

&lt;p&gt;Some systems make the correct starting point obvious. I'll call these &lt;em&gt;breadcrumb systems&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Others require exploration. I'll call these &lt;em&gt;mystery meat systems&lt;/em&gt; — borrowing the term from "mystery meat navigation," where the destination isn't visible until you hover over it. Unfortunately, mystery meat systems aren't unusual. They're common in codebases organized primarily around framework layers and persistence entities.&lt;/p&gt;

&lt;p&gt;Most real systems fall somewhere in between. &lt;/p&gt;

&lt;h2&gt;
  
  
  Readability vs. Navigability
&lt;/h2&gt;

&lt;p&gt;Designing for readability isn't just about how a file reads. It's also about how easily the system can be navigated. Navigation determines whether understanding starts immediately, or only after a search. A system can be perfectly readable and still force exploration.&lt;/p&gt;

&lt;p&gt;Readability answers: &lt;em&gt;"Can I understand this code once I'm in it?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Navigability answers: &lt;em&gt;"Can I find the right code in the first place?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Structure determines whether capability can be found by following visible boundaries or by searching and guessing.&lt;/p&gt;

&lt;p&gt;Exploration isn't inherently bad; it's how we learn new systems, and it's inevitable in legacy code. The problem is when exploration becomes the default mode of interaction: when every task begins with searching instead of navigating. When that happens, cognitive cost accumulates and people slow down. Navigability reduces &lt;em&gt;forced&lt;/em&gt; exploration; it doesn't eliminate learning.&lt;/p&gt;

&lt;p&gt;That distinction sounds subtle, but it changes the experience of working in the system. Designing code for human brains means designing how people find things, not just how they read them.&lt;/p&gt;

&lt;p&gt;When the starting point isn't obvious, you're already using working memory before you even reach the right file. You're guessing where the logic might live, opening files and reading methods that turn out to be irrelevant. The effort adds up. Finding the code becomes part of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Structure Makes Navigation Obvious
&lt;/h2&gt;

&lt;p&gt;So what does navigability look like in practice?&lt;/p&gt;

&lt;p&gt;A system is navigable when a new developer can answer these questions without asking a teammate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does this capability live?&lt;/li&gt;
&lt;li&gt;What other parts of the system participate in it?&lt;/li&gt;
&lt;li&gt;What ties those parts together?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those answers are visible in structure and vocabulary, navigation doesn't depend on tribal knowledge. This means when I'm fixing a pricing bug, I should have a reasonable starting point without asking a teammate or running a full-text search.&lt;/p&gt;

&lt;p&gt;If we want navigation instead of guesswork, capability can't live only in someone's head. It has to be visible in the structure of the codebase.&lt;/p&gt;

&lt;p&gt;At the structural level, visibility shows up in two places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capability boundaries: packages or modules that reflect what the system does, not just how it runs.&lt;/li&gt;
&lt;li&gt;Shared vocabulary:  constants, enums, and other stable identifiers that tie related pieces together across classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When both are present, readers can navigate the codebase without guesswork.&lt;/p&gt;

&lt;p&gt;When those signals are missing, mystery meat systems emerge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mystery Meat Systems
&lt;/h2&gt;

&lt;p&gt;I've noticed three anti-patterns that create mystery meat systems. Every project I've worked on in my career has included at least two of the three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anti-Pattern 1: Organization By Framework Layer
&lt;/h3&gt;

&lt;p&gt;Most modern web frameworks encourage a layered structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;controller/
service/
repository/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation is useful. It clarifies technical boundaries and keeps HTTP, application logic, and persistence concerns separate.&lt;/p&gt;

&lt;p&gt;But those layers describe &lt;em&gt;how the application runs&lt;/em&gt;, not &lt;em&gt;what the application does&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;They answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this handling a request?&lt;/li&gt;
&lt;li&gt;Is this performing application logic?&lt;/li&gt;
&lt;li&gt;Is this reading from the database?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They don't answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What feature does this represent?&lt;/li&gt;
&lt;li&gt;Where does the pricing logic live?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Framework layers are technical scaffolding: necessary, but not helpful in showing where a feature lives.&lt;/p&gt;

&lt;p&gt;This isn't an argument against layering or a specific architectural philosophy. It's about navigability: whether the structure makes the right starting point obvious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anti-Pattern 2: Organization By Persistence Entity
&lt;/h3&gt;

&lt;p&gt;In every project I've worked on, the team took layering a step further and organized within each layer by persistence entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserController
UserService
UserRepository

OrderController
OrderService
OrderRepository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That worked for small CRUD systems, but as behavior grew, the business concepts got fragmented across the stack, and the codebase got harder and harder to navigate. Technical layers and persistence entities had become the navigation model, and over time, that model stopped working.&lt;/p&gt;

&lt;p&gt;CRUD is a persistence concern, not a feature.&lt;/p&gt;

&lt;p&gt;Grouping by entity answers the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which table does this logic touch?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Grouping by feature answers a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What feature does this code implement?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most real features don't belong to a single table.&lt;/p&gt;

&lt;p&gt;Pricing, for example, may span products, discounts, contracts, and tax rules — none of which cleanly belong to a single persistence entity. Refunds may touch orders, payments, customers, and accounting entries.&lt;/p&gt;

&lt;p&gt;When everything is centered around a persistence entity, functionality tied to a single feature gets fragmented across controllers, services, and repositories. No single place represents the whole idea.&lt;/p&gt;

&lt;p&gt;Centering logic on persistence entities forces exploration. You search and guess instead of navigating to a visible boundary.&lt;/p&gt;

&lt;p&gt;In addition, classes named primarily after persistence entities and architectural roles (like &lt;code&gt;OrderService&lt;/code&gt; or &lt;code&gt;UserController&lt;/code&gt;) tend to accumulate unrelated behavior. Because the name encodes a persistence entity and architectural role rather than a feature, it doesn't constrain what belongs inside. The boundary stops signaling what belongs inside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anti-Pattern 3: Missing Shared Vocabulary
&lt;/h3&gt;

&lt;p&gt;Feature boundaries are visible in language too.&lt;/p&gt;

&lt;p&gt;In mystery meat systems, important identifiers aren't encoded consistently. Instead, they appear as scattered hard-coded values embedded directly in conditional statements across multiple files.&lt;/p&gt;

&lt;p&gt;When vocabulary isn't shared, related behavior becomes difficult to trace. Searching for functionality becomes a game of "guess and check." You look for one value, then another. You read the surrounding code to figure out if you're in the right place.&lt;/p&gt;

&lt;p&gt;Without stable identifiers, you don't have a reliable way to track down specific concepts or functionality. This forces exploration instead of navigation: searching and guessing instead of following visible boundaries.&lt;/p&gt;

&lt;p&gt;A codebase without shared vocabulary can't reliably reveal where a feature lives because the language that ties the related pieces together doesn't exist.&lt;/p&gt;

&lt;p&gt;The alternative is to make functionality visible, both structurally and linguistically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breadcrumb Systems
&lt;/h2&gt;

&lt;p&gt;In contrast, breadcrumb systems give you ways to navigate the system and find functionality without relying on another human. These systems enable navigation by organizing around features and shared vocabulary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Packages as Feature Boundaries
&lt;/h3&gt;

&lt;p&gt;Once you organize around features instead of architectural layers or tables, packages become clear mental maps.&lt;/p&gt;

&lt;p&gt;A package should answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What feature am I in?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can still keep technical layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;controller/
service/
repository/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But organize &lt;em&gt;within&lt;/em&gt; them by feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;controller/ 
  pricing/ 
    PricingController 

service/ 
  pricing/ 
    PriceCalculator 
    DiscountApplier 
    TaxCalculator 

repository/ 
  pricing/ 
    DiscountRepository 
    TaxRuleRepository 
    PriceListRepository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now "pricing" is visible across the stack. The feature becomes the breadcrumb you follow through the layers.&lt;/p&gt;

&lt;p&gt;If you're trying to fix a pricing bug, you don't scan every controller or every service. You go directly to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;controller.pricing&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service.pricing&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repository.pricing&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a breadcrumb system, you wouldn't start with &lt;code&gt;OrderService&lt;/code&gt;. You'd start with &lt;code&gt;pricing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's one way you get the benefits of proximity. Related behavior lives near related behavior.&lt;/p&gt;

&lt;p&gt;Search can find text. Structure reveals relationships. The starting point becomes obvious, and that alone changes how the system feels to work in.&lt;/p&gt;

&lt;p&gt;When packages reflect features, the system supports navigation instead of requiring exploration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: Shared Vocabulary
&lt;/h3&gt;

&lt;p&gt;Features aren't only visible in folders. They're also visible in language.&lt;/p&gt;

&lt;p&gt;In breadcrumb systems, important identifiers are defined once and referenced consistently. Enums and well-scoped constants create stable identifiers for behavior.&lt;/p&gt;

&lt;p&gt;That stability does more than improve readability. It creates structural benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traceability: related behavior can be located reliably across files.&lt;/li&gt;
&lt;li&gt;Consistency: the same concept is referenced the same way everywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a concept has a single name and a clear home, you can navigate to its behavior instead of hunting for it.&lt;/p&gt;

&lt;p&gt;Shared vocabulary turns language into infrastructure. It gives the system reliable reference points for its features.&lt;/p&gt;

&lt;p&gt;When identifiers aren't stable or specific, you lose the ability to trace functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Goes Wrong
&lt;/h2&gt;

&lt;p&gt;Like any pattern, these can be misapplied.&lt;/p&gt;

&lt;p&gt;Structure improves navigation, but only when it reduces exploration instead of adding to it. We don't want more structure; we want clearer boundaries.&lt;/p&gt;

&lt;p&gt;Every structural improvement carries risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Packages Become Taxonomy
&lt;/h3&gt;

&lt;p&gt;Packages should support navigation.&lt;/p&gt;

&lt;p&gt;Large systems will have lots of packages. The number isn't the problem. Unclear grouping that makes navigation harder instead of easier is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning signs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deep nesting without clear feature boundaries&lt;/li&gt;
&lt;li&gt;Folders organized by abstract categories instead of features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good structure narrows the search space by giving you a reliable breadcrumb. Poor structure forces you to explore. If structure increases search effort instead of narrowing it, it's working against you.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Shared Vocabulary Fails
&lt;/h3&gt;

&lt;p&gt;Introducing enums and constants isn't enough.  They can be misused, too.  Common failure modes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All identifiers are centralized in one location without regard for feature boundaries.&lt;/li&gt;
&lt;li&gt;Constants are so abstract they don't convey any feature meaning (&lt;code&gt;TRUE&lt;/code&gt;, &lt;code&gt;ZERO&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Constants are reused across unrelated functionality.&lt;/li&gt;
&lt;li&gt;Prefixes imply cohesion when they're actually unrelated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, language creates another layer of indirection instead of reducing exploration. The system looks structured, but it isn't easier to navigate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Common Thread
&lt;/h3&gt;

&lt;p&gt;Across all of these patterns, the failure is the same: instead of improving navigation, the structure increases indirection.&lt;/p&gt;

&lt;p&gt;The goal isn't to add more folders or identifiers. The goal is to make features visible without requiring someone to trace execution across or search the entire system.&lt;/p&gt;

&lt;p&gt;Good structure narrows the search space and supports navigation. Poor structure expands the search space and pushes readers into guesswork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breadcrumb Systems Can Emerge Gradually
&lt;/h2&gt;

&lt;p&gt;Most systems don't start out with breadcrumbs.&lt;/p&gt;

&lt;p&gt;On Taz, our system wasn't always organized around feature boundaries. For years, it followed the standard Java/Spring layering pattern — controllers, services, repositories — grouped by persistence entity. That's a common default, and it worked well enough early on.&lt;/p&gt;

&lt;p&gt;But as the system grew, navigation became harder. Behavior related to a single feature was scattered across layers and files. Finding the right starting point required searching and guesswork.&lt;/p&gt;

&lt;p&gt;About ten years in, we started introducing a new package structure organized around feature boundaries. The legacy packages remained layered by entity, but new features were built using feature boundaries. And as we refactored old code, we moved it into the new structure.&lt;/p&gt;

&lt;p&gt;The system didn't flip overnight. For a while, it was part mystery meat, part breadcrumb.&lt;/p&gt;

&lt;p&gt;But over time, the navigable surface area grew. Exploration shrank. The default starting point became clearer. Instead of accumulating entropy, the system gradually became easier to understand.&lt;/p&gt;

&lt;p&gt;You don't need perfect architecture to improve navigability. Even inside a legacy system, you can begin introducing visible feature boundaries and shared vocabulary. Each new boundary reduces search space for the next person who has to read the code.&lt;/p&gt;

&lt;p&gt;Navigability compounds over time. Small structural improvements, repeated consistently, shift how the entire system feels to work in.&lt;/p&gt;

&lt;p&gt;Even if you're currently in a mystery meat system, you can start leaving breadcrumbs now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Microservices?
&lt;/h2&gt;

&lt;p&gt;The same principle applies even when the system is distributed.&lt;/p&gt;

&lt;p&gt;In distributed systems, cross-service navigation is inherently harder. Some exploration is unavoidable. But inside each boundary, navigability is still possible.&lt;/p&gt;

&lt;p&gt;You may not control the entire ecosystem. You may not be able to reorganize every microservice. But you can make the ones you touch internally navigable, with visible feature boundaries and shared vocabulary.&lt;/p&gt;

&lt;p&gt;Global coherence may be too lofty a goal, but local coherence will help shrink the search space for the next reader.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigation Is The First Step
&lt;/h2&gt;

&lt;p&gt;Organizing around feature boundaries makes it possible to find the right part of the system. But finding the right file doesn't mean the file itself is readable.&lt;/p&gt;

&lt;p&gt;Once you're inside a package, the question changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this class make its purpose visible?&lt;/li&gt;
&lt;li&gt;Or do you still have to simulate execution to understand it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Structure determines whether you can find the code. Cohesion determines whether you can understand it. Navigability gets you to the right room; cohesion determines whether the room makes sense once you're inside it.&lt;/p&gt;

&lt;p&gt;When finding the right code takes longer, every review, bug fix, and feature change slows down because the starting point is unclear.&lt;/p&gt;

&lt;p&gt;If software performance depends on how quickly a team can understand and safely change a system, then navigability becomes part of its performance. The faster the right starting point can be located, the less cognitive overhead a change requires.&lt;/p&gt;

&lt;p&gt;Navigation is step one. Cohesion is step two.&lt;/p&gt;

&lt;p&gt;That leads directly to the Single Responsibility Principle as a design tool for reducing mental load, which will be my next post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI was my editor, but these ideas are my own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This article is part of a broader series exploring how code structure, navigability, and cohesion align with cognitive limits.&lt;/p&gt;

&lt;p&gt;If you're interested in the deeper dive, the full series is here:&lt;br&gt;
&lt;a href="https://christiecosky.com/series/designing-systems-for-human-brains/" rel="noopener noreferrer"&gt;Designing Systems for Human Brains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>readability</category>
      <category>maintainability</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Readability Is a Performance Constraint</title>
      <dc:creator>Christie Cosky</dc:creator>
      <pubDate>Mon, 02 Mar 2026 00:43:37 +0000</pubDate>
      <link>https://dev.to/christiecosky/readability-is-a-performance-constraint-15fa</link>
      <guid>https://dev.to/christiecosky/readability-is-a-performance-constraint-15fa</guid>
      <description>&lt;p&gt;Most teams obsess over execution speed: build speed, runtime performance, delivery velocity.&lt;/p&gt;

&lt;p&gt;But there's another bottleneck we rarely measure: how long it takes a human to understand the code.&lt;/p&gt;

&lt;p&gt;Open a file during a production incident. The clock is ticking. You're scanning for the bug. If the structure is unclear, you waste time just figuring out what you're looking at.&lt;/p&gt;

&lt;p&gt;Readability is more than style. It's a performance constraint.&lt;/p&gt;

&lt;p&gt;Software development is a read-heavy discipline. Code is written once and read repeatedly — during code reviews, debugging sessions, refactors, feature enhancements, and production incidents, often under time pressure. When code is hard to read, understanding slows down. When understanding slows down, velocity drops and risk increases.&lt;/p&gt;

&lt;p&gt;To be clear, there are contexts where optimizing for speed is rational. But when the code is expected to change and grow over time, readability isn't optional overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Depends on Reading, Not Writing
&lt;/h2&gt;

&lt;p&gt;We write code once, but we read it for years.&lt;/p&gt;

&lt;p&gt;The majority of software development effort isn't typing new logic; it's reconstructing mental models while reading what already exists. If most engineering time is spent reading and modifying existing code, that's the hot path. Even small reductions in comprehension time compound across reviews, fixes, and feature changes.&lt;/p&gt;

&lt;p&gt;The bottleneck isn't typing speed; it's the speed of reading and understanding. In a read-heavy system, that makes readability a performance constraint, not a stylistic preference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Bottleneck Is Perception
&lt;/h2&gt;

&lt;p&gt;We don't read code line-by-line like a compiler. Structure is processed before logic.&lt;/p&gt;

&lt;p&gt;When structure is unclear, working memory fills with extraneous cognitive load, leaving less capacity to reason about the code itself. It's like running too many programs at once: performance drops.&lt;/p&gt;

&lt;p&gt;Unreadable code slows understanding by consuming the cognitive resources required to make correct decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing for Writes Slows the System
&lt;/h2&gt;

&lt;p&gt;In cultures that prize speed above all else, teams often optimize for writes. Code optimized for writes often lacks clarity, and much of the context lives only in the author's head. In these environments, short-term urgency consistently outruns long-term maintainability.&lt;/p&gt;

&lt;p&gt;The cost of re-reading gets pushed downstream — to reviewers, on-call teammates, and future maintainers. They pay the cognitive tax. Over time, that compounds.&lt;/p&gt;

&lt;p&gt;Code optimized for reads may take longer to write, but it shortens time-to-understanding, reduces review friction, and lowers change risk.&lt;/p&gt;

&lt;p&gt;This isn't polishing; it's throughput protection.&lt;/p&gt;

&lt;p&gt;Readability is an up-front investment that shifts cost left. It increases writing time slightly in exchange for reducing every future interaction cost — review time, debugging time, onboarding time, and refactoring time. In systems that are read repeatedly, that trade pays for itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unreadable Code Concentrates Knowledge — and Risk
&lt;/h2&gt;

&lt;p&gt;Most teams have at least one feature that "belongs" to a single developer. It's usually difficult to understand and completely avoided by the rest of the team. We joke that writing code nobody else understands is "job security." It stops being funny when that person leaves.&lt;/p&gt;

&lt;p&gt;Unreadable code concentrates knowledge. When understanding is expensive, fewer people are willing to pay the cost. Ownership narrows, incidents escalate to the one person who "knows how it works," and refactors are avoided because the risk feels too high.&lt;/p&gt;

&lt;p&gt;That's not an aesthetics problem. That's a risk concentration problem.&lt;/p&gt;

&lt;p&gt;Over time, this increases the "bus factor": the number of people who would need to be hit by a bus before the system becomes unmaintainable.&lt;/p&gt;

&lt;p&gt;Readable code lowers the energy required to understand and modify a feature. Knowledge spreads. Ownership widens. Operational risk decreases.&lt;/p&gt;

&lt;p&gt;Readability creates redundancy in human understanding — and redundancy is how systems stay resilient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure Exposes Errors
&lt;/h2&gt;

&lt;p&gt;When logic follows consistent patterns (using perceptual principles like repetition, hierarchy, and alignment), inconsistencies become visible. When I can see the patterns in my code, problems sometimes jump out: "Duh, how did I miss that?" Well-structured code allows the pattern-matching brain to notice what doesn't match.&lt;/p&gt;

&lt;p&gt;In code reviews, predictable structure lets reviewers safely scan for inconsistencies instead of reconstructing the entire mental model from scratch. When structure is unclear, bugs hide in the noise and are discovered in QA or production later — when the context has faded and the cost of fixing them is higher.&lt;/p&gt;

&lt;p&gt;Readable code prevents mistakes and makes them visible while they're still cheap to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experience Shifts the Optimization Target
&lt;/h2&gt;

&lt;p&gt;Over time, many developers learn the same lesson: unreadable code is expensive.&lt;/p&gt;

&lt;p&gt;They inherit parts of the codebase that technically "work" but are impossible to understand. They review changes that take longer to understand than they did to write. They refactor code whose original intent is unclear.&lt;/p&gt;

&lt;p&gt;Experience changes the optimization target.&lt;/p&gt;

&lt;p&gt;Early in my career, I optimized for speed. I was praised for finishing tasks quickly, so that's what I focused on. When I look back at some of my early personal projects, I see god classes, massive methods, and long unbroken walls of code. It worked — but it was optimized for writes, not reads.&lt;/p&gt;

&lt;p&gt;Over time, my priorities changed from local velocity ("How fast can I finish?") to system velocity ("How easy can I make this to understand and change in the future?"). &lt;/p&gt;

&lt;p&gt;Many experienced developers make that shift — not because they became perfectionists, but because they've paid the cost of unreadable code.&lt;/p&gt;

&lt;p&gt;Some developers argue that working code can be cleaned up later. In practice, this rarely happens. Cleanup competes with new deadlines, and new deadlines almost always take precedence. (As I like to say, entropy always wins!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Readability Is a Team Multiplier
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Code Reviews
&lt;/h3&gt;

&lt;p&gt;Readable code lowers the cost of understanding during review. When the structure and intent are clear, reviewers can focus on correctness instead of reconstructing the mental model from scratch. Reviews become faster and more effective.&lt;/p&gt;

&lt;p&gt;Unreadable code does the opposite. Large, dense changes increase review fatigue and time-to-understanding. When understanding is expensive, reviewers conserve their energy. They skim when it isn't safe to do so and approve changes they don't fully understand.&lt;/p&gt;

&lt;p&gt;Readable code increases the probability that reviews actually catch the right problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coordination Cost
&lt;/h3&gt;

&lt;p&gt;When intent is visible in the code, fewer meetings are required to explain it. Developers don't need to ask for walkthroughs of control flow just to make a small change.&lt;/p&gt;

&lt;p&gt;When it's not, meetings and messages are required to answer questions — often multiple times to multiple developers over time. The knowledge lives in chat messages and in the original author's head.&lt;/p&gt;

&lt;p&gt;Readability reduces repeat explanations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load Distribution
&lt;/h3&gt;

&lt;p&gt;In unreadable codebases, certain developers become the "translators" of the logic. They are pulled into meetings, pinged constantly, and have their time consumed by answering questions and validating changes. They become bottlenecks. &lt;/p&gt;

&lt;p&gt;Readable systems distribute cognitive load. Changes aren't dependent on a single person's availability. That's not just a bus factor issue; it's structural load-balancing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Onboarding Velocity
&lt;/h3&gt;

&lt;p&gt;In a readable system, a new developer can build a mental model by skimming structure and names. In an unreadable one, onboarding requires synchronous explanations and tribal knowledge. Understanding doesn't scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Psychological Safety
&lt;/h3&gt;

&lt;p&gt;When developers struggle to understand code, they often blame themselves. They question their competence, hesitate to ask questions, and avoid touching risky areas as much as they can. That erodes morale and confidence and can eventually affect retention. I have experienced this myself.&lt;/p&gt;

&lt;p&gt;Readable systems reinforce competence. Developers can understand, complete their work independently, and contribute without fear. And developers who aren't afraid to touch the code move faster, ask better questions, and take ownership more readily — which feeds directly back into throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scale Requires Consistency
&lt;/h2&gt;

&lt;p&gt;As systems grow, inconsistency becomes more expensive.&lt;/p&gt;

&lt;p&gt;In a small codebase with a small team, structural differences are tolerable. Context lives in shared memory and conversation. But as the codebase grows, the team grows, and time passes, that tolerance disappears.&lt;/p&gt;

&lt;p&gt;Large codebases rely on shared patterns, not shared memory.&lt;/p&gt;

&lt;p&gt;When similar problems are solved in similar ways, developers learn how to read the system. Patterns become familiar, and structure becomes predictable. They know where to look and what shape new code should take. When they add their own changes, they reinforce those patterns instead of inventing new ones.&lt;/p&gt;

&lt;p&gt;Inconsistent systems are unfamiliar and unpredictable. Each file must be reinterpreted from scratch. Every change requires learning a new pattern. Developers hesitate to make changes or require validation from others that their changes are correct. Understanding doesn't accumulate; it resets. That re-learning cost grows with the size of the system.&lt;/p&gt;

&lt;p&gt;You can't scale shared understanding without structural consistency.&lt;/p&gt;

&lt;p&gt;Without it, every change requires deep context reconstruction. With it, understanding becomes incremental, and incremental understanding is what makes large systems sustainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Is the Performance Constraint
&lt;/h2&gt;

&lt;p&gt;Velocity is often measured in outputs: tickets closed, commits merged, lines of code written. Those metrics optimize for writes — short-term wins and visible activity.&lt;/p&gt;

&lt;p&gt;Comprehension speed is invisible.&lt;/p&gt;

&lt;p&gt;If we could measure read-optimized velocity, we'd track how quickly a human can understand and safely change the code.&lt;/p&gt;

&lt;p&gt;Code volume is increasing rapidly. I recently generated 11,000 lines of code (including comments and tests) in three days using AI tooling. It took me more than twice as long to review, restructure, and refactor it into something I actually understood.&lt;/p&gt;

&lt;p&gt;Writing has never been the primary constraint. Understanding has.&lt;/p&gt;

&lt;p&gt;AI removes friction from writing, but the underlying performance constraint hasn't changed. If organizations don't rebalance toward comprehension, the gap between write speed and understanding speed widens — and velocity eventually slows under its own weight.&lt;/p&gt;

&lt;p&gt;Experienced teams stay fast because of readable code, not in spite of taking the time to write it that way.&lt;/p&gt;

&lt;p&gt;If performance is constrained by understanding, then structure isn't cosmetic. It's throughput protection.&lt;/p&gt;

&lt;p&gt;AI is accelerating write velocity. But review, comprehension, and safe change still depend on human cognition — and cognition doesn't scale with code volume.&lt;/p&gt;

&lt;p&gt;That makes readability more important now than it was five years ago, not less.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI was my editor, but these ideas are my own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This article is part of a broader series exploring how code structure, navigability, and cohesion align with cognitive limits.&lt;/p&gt;

&lt;p&gt;If you're interested in the deeper dive, the full series is here:&lt;br&gt;
&lt;a href="https://christiecosky.com/series/designing-systems-for-human-brains/" rel="noopener noreferrer"&gt;Designing Systems for Human Brains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>codequality</category>
      <category>readability</category>
    </item>
  </channel>
</rss>
