<?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: Dexterlung</title>
    <description>The latest articles on DEV Community by Dexterlung (@dexterlung).</description>
    <link>https://dev.to/dexterlung</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4029426%2F0d3f03e5-03d2-46ff-975f-c565c23e82ce.jpg</url>
      <title>DEV Community: Dexterlung</title>
      <link>https://dev.to/dexterlung</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dexterlung"/>
    <language>en</language>
    <item>
      <title>The same bug, fixed three times: all my guards lived on the write side</title>
      <dc:creator>Dexterlung</dc:creator>
      <pubDate>Wed, 15 Jul 2026 02:08:51 +0000</pubDate>
      <link>https://dev.to/dexterlung/the-same-bug-fixed-three-times-all-my-guards-lived-on-the-write-side-1ajf</link>
      <guid>https://dev.to/dexterlung/the-same-bug-fixed-three-times-all-my-guards-lived-on-the-write-side-1ajf</guid>
      <description>&lt;p&gt;There's a kind of bug that isn't hard to fix — it's just that it &lt;em&gt;comes back&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I run a small coffee e-commerce platform, alone. One inventory bug had a very simple symptom: a coffee product was actually sold out. The admin panel showed it correctly — sold out. But a customer opening the storefront on their phone could still add it to the cart and place an order. Selling something you don't have is the kind of thing that makes an e-commerce person break into a cold sweat.&lt;/p&gt;

&lt;p&gt;The first time I found it, I fixed it fast. The second time it showed up somewhere else, I fixed it again. The third time — when I noticed I was fixing "sold-out product is still orderable" for the &lt;em&gt;third&lt;/em&gt; time — I stopped. Because a fourth time was just a matter of when.&lt;/p&gt;

&lt;p&gt;That was the moment I realized: I shouldn't be fixing this bug. I should be asking &lt;strong&gt;why it keeps coming back.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven exits, the override patched on half of them
&lt;/h2&gt;

&lt;p&gt;The stock status came out of one function — call it &lt;code&gt;getProductStockStatus&lt;/code&gt;. Given green-bean stock, roasted-bean stock, and product state, it returned a verdict: is this product &lt;code&gt;hidden&lt;/code&gt;, &lt;code&gt;needs-roasting&lt;/code&gt;, &lt;code&gt;sold-out&lt;/code&gt;, or &lt;code&gt;sellable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem was how it was written. It wasn't computing a result and returning once at the end — it was a chain of early returns. Hit a condition, &lt;code&gt;return&lt;/code&gt; right there, skip the rest. Nothing wrong with that; lots of people write it that way and it reads fine.&lt;/p&gt;

&lt;p&gt;But I had one extra requirement: I (the owner) can manually mark a product "sold out" in the admin panel, and that manual flag must override whatever the stock math says. So the manual-override check had to be pasted in front of &lt;em&gt;every&lt;/em&gt; branch that could return "sellable."&lt;/p&gt;

&lt;p&gt;That function had seven early-return branches.&lt;/p&gt;

&lt;p&gt;You can probably guess what happened. Every time I added a new branch, I had a new exit, and I had to remember to paste the override check into that new exit too. I missed it three times. The first two were the branches where an anonymous user couldn't read the underlying bean-stock table — the function decided "can't read the base data" and returned early, before ever reaching the override. The third was a branch I added for an unrelated stock tweak, and forgot again.&lt;/p&gt;

&lt;p&gt;Each "fix" was, in essence, going to the missed exit and pasting the override one more time. Tests were green right after, because I was testing the branch I'd just patched. But the moment I added the next exit, the bug had a seat reserved for its fourth revival. I wasn't curing it. I was re-manufacturing the exact conditions for the same bug, the exact same way, over and over.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause wasn't in the bug. It was in how I fixed it.
&lt;/h2&gt;

&lt;p&gt;The real fix is obvious once you say it out loud: &lt;strong&gt;don't have seven exits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I refactored so every non-&lt;code&gt;hidden&lt;/code&gt; branch returns the same &lt;code&gt;finalize()&lt;/code&gt; function instead of returning a result directly. The override check lives inside &lt;code&gt;finalize()&lt;/code&gt; — pasted exactly once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before: seven branches each return, override pasted 7x (missed 3)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;soldOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;- miss 1, 2 (anon can't read bean stock)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;someOtherCondition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;       &lt;span class="c1"&gt;// &amp;lt;- miss 3 (branch added later)&lt;/span&gt;
&lt;span class="c1"&gt;// ...four more&lt;/span&gt;

&lt;span class="c1"&gt;// after: every non-hidden branch goes through one exit; override applied once&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manualSoldOut&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;soldOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;// the only override point&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// each branch: return finalize({ ... })&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behavior is identical — all hundred-plus existing tests stayed green. But structurally something changed: &lt;strong&gt;from now on, no matter how many branches I add, as long as it goes through &lt;code&gt;finalize()&lt;/code&gt;, the override is applied. A new branch can't miss it, because there's no longer a "miss" action available.&lt;/strong&gt; I turned a contract that depended on my memory into something you &lt;em&gt;can't&lt;/em&gt; forget to do.&lt;/p&gt;

&lt;p&gt;Then I pinned a test on the real function (not a drifted copy — I later found I had a test copy that didn't match the real function, and its branch was missing the override, so it was testing a fake). Assert: every non-hidden branch + manual sold-out → result is sold-out. I also did a mutation check: I manually reverted the refactor back to the missed early return, and the test went red immediately, including the anon point that caused the recurrences. Confirming the test actually bites, instead of being decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  All my defenses lived on the write side
&lt;/h2&gt;

&lt;p&gt;After collapsing those seven exits, I didn't stop — because a bigger problem had surfaced.&lt;/p&gt;

&lt;p&gt;I'd built a whole governance layer for this system: single-source-of-truth discipline, cross-layer data-contract audits, a thing I call &lt;em&gt;trace lock&lt;/em&gt; (register a cross-layer data flow and pin it with a test so "changed A, forgot B" can't happen silently). I'm proud of it. It's caught a lot of cross-module breakage.&lt;/p&gt;

&lt;p&gt;But staring at this bug that came back three times, something uncomfortable clicked: &lt;strong&gt;every anchor of my defense was on the &lt;em&gt;write&lt;/em&gt; side.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All my rules govern "how does data get written," "is the write → middleware → render chain intact." The trace anchors are all write-side sources of truth. That approach kills a huge class of "the thing that got written is wrong" bugs.&lt;/p&gt;

&lt;p&gt;But this inventory bug wasn't a write problem. The stock number in the DB was correct. The product state was correct. The manual sold-out flag was stored fine. The write side was flawless. The bug lived on the &lt;strong&gt;read side&lt;/strong&gt; — the same stock data, read separately by many screens, each computing status its own way. My write-side guards simply don't reach the read surface.&lt;/p&gt;

&lt;p&gt;And there's a nastier variant I now call &lt;strong&gt;auth-dependent SSOT&lt;/strong&gt;. On the surface every screen shares the same &lt;code&gt;getProductStockStatus&lt;/code&gt; function — looks fully single-sourced, looks converged to one place. But one of its inputs is a table only admins can read (bean-stock tables locked to &lt;code&gt;is_admin()&lt;/code&gt; via row-level security). So the &lt;em&gt;same function&lt;/em&gt;, fed an admin identity, computes "sold out"; fed an anonymous identity — because it can't read the bean data and returns early — computes "sellable."&lt;/p&gt;

&lt;p&gt;Same function, different truth per identity. That's harder to catch than "two screens each rolling their own," because it &lt;em&gt;looks&lt;/em&gt; like a single source already, so you don't suspect it. The heuristic I wrote down: &lt;strong&gt;"admin sees OK, anon sees broken" almost always means the frontend is computing from data only admins can read.&lt;/strong&gt; The real fix isn't converging the function one more time — it's moving that computation to the database and storing it as a column anonymous users can read too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three gaps I left open (on purpose)
&lt;/h2&gt;

&lt;p&gt;Laid out flat, this recurrence exposed three mechanisms my tooling still lacks — and honestly, all three are still unbuilt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recurrence counting.&lt;/strong&gt; My sentinel tools look at recent commit windows, but they don't track "how many times have I fixed this &lt;em&gt;class&lt;/em&gt; of bug in six months." If it had told me "this is your third sold-out fix," I'd have stopped at the second. It needs a recurrence ledger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root-cause forcing.&lt;/strong&gt; I have tools that remind me after a fix to "go check for sibling structures with the same flaw," but they're all reactive. No rule proactively stops me at "you only fixed one read-side entry — what about the others?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-surface auto-extension.&lt;/strong&gt; My trace lock only verifies that &lt;em&gt;registered&lt;/em&gt; readers stay consistent. It doesn't scan for readers I forgot to register. That unregistered entry is exactly where the next recurrence breeds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wrote all three into my tech-debt backlog, high priority, and deliberately didn't rush to build them. The most valuable thing for a solo dev isn't patching every hole the moment you see it — it's seeing that the holes are all the same shape, then deciding which one is worth closing first.&lt;/p&gt;

&lt;p&gt;But the inventory function itself is genuinely cured now. Not because I was more careful this time — because I finally stopped fixing it by "pasting the override into the exit I missed." When you catch yourself fixing the same bug a third time the same way, that bug stopped being a bug a while ago. The way you fix it is the bug.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>postgressql</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
