<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Matthew Boston</name>
  </author>
  <title>Matthew Boston</title>
  <subtitle>Continuous improvement of software and people</subtitle>
  <id>https://matthewboston.com</id>
  <link href="https://matthewboston.com"/>
  <link href="https://matthewboston.com/blog"/>
  <link href="https://matthewboston.com/feed.xml" rel="self"/>
  <updated>2026-04-21T00:00:00Z</updated>
  <entry>
    <title>Building a Platform Is a Balancing Act</title>
    <link rel="alternate" href="https://matthewboston.com/blog/building-a-platform-is-a-balancing-act.html"/>
    <id>https://matthewboston.com/blog/building-a-platform-is-a-balancing-act.html</id>
    <published>2026-04-21T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Every platform team lives between two failure modes: say yes to everything and drown in custom code, or say no to everything and watch your users route around you.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="two-kinds-of-dead-platforms"&gt;Two Kinds of Dead Platforms&lt;/h2&gt;

&lt;p&gt;The first kind of dead platform is the one that said yes too often. Every team got their special flag, their bespoke deployment path, their one-off integration. From the outside it looks like a platform. From the inside it’s twelve platforms duct-taped together, each maintained by someone who left two years ago. Changes take weeks because every change risks breaking someone’s snowflake.&lt;/p&gt;

&lt;p&gt;The second kind of dead platform is the one that said no too often. It had a clean abstraction, a beautiful API, and exactly three users — because every other team found it faster to build their own thing than to wait six months for a feature request to land. The platform team is still shipping. Nobody’s using it.&lt;/p&gt;

&lt;p&gt;Both failures start with the same moment: a team shows up with a request that doesn’t fit. What you do next is the whole job.&lt;/p&gt;

&lt;h2 id="the-pull-toward-yes"&gt;The Pull Toward Yes&lt;/h2&gt;

&lt;p&gt;Saying yes feels good. It makes a user happy today. It proves the platform is “flexible.” It avoids the awkward conversation where you explain why their very reasonable-sounding request is actually going to cost you six months of maintenance burden.&lt;/p&gt;

&lt;p&gt;But every yes is a promise. Not just to ship the feature — to support it, document it, migrate it, and keep it working when the next version ships. A platform with a hundred features has a hundred promises. Most of them will outlive the person who made them.&lt;/p&gt;

&lt;p&gt;The teams asking for custom behavior aren’t wrong to ask. They have real problems. The mistake is treating every real problem as something the platform must solve &lt;em&gt;in the shape the user proposed&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id="the-pull-toward-no"&gt;The Pull Toward No&lt;/h2&gt;

&lt;p&gt;Saying no feels principled. It protects the abstraction. It keeps the surface area small. It lets the platform team sleep at night.&lt;/p&gt;

&lt;p&gt;But a platform that only solves the problems its designers anticipated isn’t a platform — it’s a framework for the problems that already existed when it was built. Real users have real needs that weren’t in the original design doc. Refusing to evolve is just a slower way of dying.&lt;/p&gt;

&lt;p&gt;“No” is also expensive in a way that doesn’t show up on the platform team’s dashboard. It shows up as shadow infrastructure, forked repos, and the quiet realization that half the company has stopped using you.&lt;/p&gt;

&lt;h2 id="snowflakes-arent-snowflakes"&gt;Snowflakes Aren’t Snowflakes&lt;/h2&gt;

&lt;p&gt;Here’s the pattern I’ve seen over and over: the “unique” request isn’t unique. The team asking for a custom retry policy is the third team this quarter to ask for one — they just phrased it differently. The team that needs a special deployment hook has a problem that’s one config flag away from the team that asked last month.&lt;/p&gt;

&lt;p&gt;Snowflakes feel unique to the person holding them. From the platform’s vantage point, they almost always cluster. Two requests are a coincidence. Three requests are a feature. Five requests are a design flaw you haven’t fixed yet.&lt;/p&gt;

&lt;p&gt;This is the same &lt;a href="/blog/over-engineering-is-just-future-proofing-gone-too-far"&gt;Rule of Three&lt;/a&gt; that keeps you from over-engineering on day one. It also keeps you from under-engineering on day three hundred. The first request is a conversation. The second is a pattern forming. The third is a signal that the platform is missing something real.&lt;/p&gt;

&lt;h2 id="the-real-job-finding-the-pattern"&gt;The Real Job: Finding the Pattern&lt;/h2&gt;

&lt;p&gt;The job isn’t saying yes or no. The job is translation.&lt;/p&gt;

&lt;p&gt;When a team shows up with a custom request, the first question isn’t “should we build this?” It’s “what’s the general shape of this problem, and how many other teams have it?” The answer determines the response:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Truly one-off:&lt;/strong&gt; Help them solve it outside the platform. Not every problem belongs to you.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;One of a few:&lt;/strong&gt; Say no for now, but track it. Two more and you have a pattern.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Recurring:&lt;/strong&gt; Build it into the platform properly. Not as a flag for this team, but as a first-class capability everyone can use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst answer is the middle path that looks like a compromise: a special flag, a hidden config, a “temporary” exception that becomes permanent. That’s how platforms accumulate the snowflakes that kill them.&lt;/p&gt;

&lt;h2 id="abstractions-earn-their-keep"&gt;Abstractions Earn Their Keep&lt;/h2&gt;

&lt;p&gt;Good platform abstractions don’t come from whiteboard sessions. They come from watching three teams solve the same problem three different ways and extracting what’s common. You can’t design that upfront — you have to earn it through contact with real users.&lt;/p&gt;

&lt;p&gt;Which means the platform team’s instinct to protect the abstraction is right &lt;em&gt;and&lt;/em&gt; wrong. Right, because abstractions are load-bearing. Wrong, because abstractions that never evolve become museums. The abstraction you shipped last year was based on the problems you understood last year. The problems got more interesting. The abstraction should too.&lt;/p&gt;

&lt;h2 id="yes-no-and-not-yet"&gt;Yes, No, and Not Yet&lt;/h2&gt;

&lt;p&gt;The most useful word in a platform team’s vocabulary isn’t yes or no. It’s &lt;em&gt;not yet&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;“Not yet” says: I see your problem, I believe it’s real, and I’m going to wait until I see it from enough angles to build the right thing instead of the fast thing. It’s harder than yes. It’s more honest than no. And it’s the only answer that scales.&lt;/p&gt;

&lt;p&gt;Platforms die when teams stop being willing to say any of the three. Yes-only platforms collapse under their own weight. No-only platforms get abandoned. Not-yet-only platforms become irrelevant. The balancing act is knowing which answer this request — and this moment — calls for.&lt;/p&gt;

&lt;p&gt;The platform that lasts is the one where every yes was earned, every no was explained, and every not-yet eventually turned into one or the other.&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Don't Just Build with AI — Learn Through It</title>
    <link rel="alternate" href="https://matthewboston.com/blog/dont-just-build-with-ai-learn-through-it.html"/>
    <id>https://matthewboston.com/blog/dont-just-build-with-ai-learn-through-it.html</id>
    <published>2026-04-14T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Most developers use AI agents to ship faster. Fewer use them to &lt;em&gt;learn&lt;/em&gt; faster. That’s the bigger missed opportunity.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-default-mode-is-wrong"&gt;The Default Mode Is Wrong&lt;/h2&gt;

&lt;p&gt;The typical workflow with an AI coding agent goes like this: describe the feature, let the agent generate code, review the output, ship it. It works. But it skips the most valuable step – understanding &lt;em&gt;why&lt;/em&gt; the codebase looks the way it does.&lt;/p&gt;

&lt;p&gt;When you’re onboarding to a new codebase, the temptation is to let the agent handle everything you don’t understand yet. Need to add a feature in an unfamiliar module? Let the agent figure out the patterns. Don’t know the naming conventions? The agent will match what’s already there. This gets you to a working PR, but it doesn’t get you to understanding.&lt;/p&gt;

&lt;p&gt;And without understanding, you’re &lt;a href="/blog/speed-is-only-useful-if-youre-going-in-the-right-direction"&gt;going fast in the wrong direction&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="pair-programming-with-infinite-patience"&gt;Pair Programming with Infinite Patience&lt;/h2&gt;

&lt;p&gt;Here’s what I do instead: I use the agent as an interrogation partner. Before asking it to write anything, I ask it to &lt;em&gt;explain&lt;/em&gt; things.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Why is this module structured this way?&lt;/li&gt;
  &lt;li&gt;What design pattern is this service using, and what problem does it solve?&lt;/li&gt;
  &lt;li&gt;Why does this test mock this dependency but not that one?&lt;/li&gt;
  &lt;li&gt;What would break if I changed this interface?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No human pair partner has the patience for this many questions. An AI agent does. It’ll explain the same concept ten different ways without sighing. It’ll trace a function call through six files without losing track. It’ll compare two approaches and explain the tradeoffs of each without checking the clock.&lt;/p&gt;

&lt;p&gt;This is pair programming without the social cost of asking “dumb” questions.&lt;/p&gt;

&lt;h2 id="ask-for-architecture-not-just-implementations"&gt;Ask for Architecture, Not Just Implementations&lt;/h2&gt;

&lt;p&gt;The questions that build real understanding aren’t about syntax or APIs. They’re about &lt;em&gt;decisions&lt;/em&gt;. Every codebase is a fossil record of choices – some deliberate, some accidental, some inherited from a framework the team adopted three years ago. Understanding those choices is what separates someone who can modify the code from someone who truly &lt;em&gt;knows&lt;/em&gt; the system.&lt;/p&gt;

&lt;p&gt;Ask the agent to explain the architecture. Ask why the team chose this database over that one. Ask what the test strategy is and whether it’s consistent. Ask about the error handling patterns and whether they match across services.&lt;/p&gt;

&lt;p&gt;This is the &lt;a href="/blog/a-bad-line-of-research-is-a-hundred-bad-lines-of-code"&gt;research phase&lt;/a&gt; applied to your own learning – and it compounds just as fast. Every question builds context. Every answer connects to the next question. Within a few sessions, you’ll have a mental model of the system that would have taken weeks to build through code reading alone.&lt;/p&gt;

&lt;h2 id="the-learning-compounds"&gt;The Learning Compounds&lt;/h2&gt;

&lt;p&gt;There’s a second-order effect here. Once you understand the codebase deeply, you become a better collaborator &lt;em&gt;with&lt;/em&gt; the agent. You write better prompts because you know the vocabulary. You catch mistakes faster because you know the patterns. You &lt;a href="/blog/if-you-cant-one-shot-your-feature-its-a-skill-md-issue"&gt;capture what you learn&lt;/a&gt; in SKILL.md and CLAUDE.md files, which makes the agent smarter in future sessions.&lt;/p&gt;

&lt;p&gt;Understanding begets better tooling begets faster understanding. It’s a flywheel – but only if you invest in the learning side, not just the building side.&lt;/p&gt;

&lt;h2 id="understanding-is-the-job"&gt;Understanding Is the Job&lt;/h2&gt;

&lt;p&gt;The engineers who get the most from AI aren’t the ones who delegate the most. They’re the ones who learn the most. Every question you ask the agent is an investment in your own judgment – the one thing AI can’t replace.&lt;/p&gt;

&lt;p&gt;Don’t just use AI to write code you don’t understand. Use it to build understanding you couldn’t get any other way.&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Speed Is Only Useful If You're Going in the Right Direction</title>
    <link rel="alternate" href="https://matthewboston.com/blog/speed-is-only-useful-if-youre-going-in-the-right-direction.html"/>
    <id>https://matthewboston.com/blog/speed-is-only-useful-if-youre-going-in-the-right-direction.html</id>
    <published>2026-03-31T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Everyone’s optimizing for speed. Faster deployments, faster iterations, faster code generation. But speed without direction is just expensive wandering.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-velocity-trap"&gt;The Velocity Trap&lt;/h2&gt;

&lt;p&gt;There’s a seductive metric in software engineering: how fast can we ship? Teams measure cycle time, deploy frequency, and lines of code per sprint. AI tools promise 100x productivity. And all of it assumes that going faster is inherently better.&lt;/p&gt;

&lt;p&gt;But velocity is a vector. It has magnitude &lt;em&gt;and&lt;/em&gt; direction. A team shipping features at breakneck speed toward the wrong goal isn’t productive — they’re accumulating debt they don’t even know about yet.&lt;/p&gt;

&lt;h2 id="fast-in-the-wrong-direction"&gt;Fast in the Wrong Direction&lt;/h2&gt;

&lt;p&gt;I’ve seen teams build entire systems in weeks using AI-assisted tools, only to realize they solved a problem no one had. The code was clean. The tests passed. The architecture was sound. And none of it mattered because they started building before they understood what they were building &lt;em&gt;for&lt;/em&gt;. This is the same dynamic behind the &lt;a href="/blog/a-bad-line-of-research-is-a-hundred-bad-lines-of-code"&gt;Research-Plan-Implement discipline&lt;/a&gt; — a bad line of research sends everything downstream in the wrong direction.&lt;/p&gt;

&lt;p&gt;Speed amplifies whatever direction you’re already headed. If your direction is right, speed is a superpower. If your direction is wrong, speed just gets you lost faster.&lt;/p&gt;

&lt;h2 id="the-cost-of-rework"&gt;The Cost of Rework&lt;/h2&gt;

&lt;p&gt;Rework is the tax you pay for moving fast without clarity. And it’s not just the time to redo the work — it’s the opportunity cost of what you could have built instead, the morale cost of throwing away effort, and the trust cost when stakeholders see the same problem “solved” twice.&lt;/p&gt;

&lt;p&gt;A team that spends two days thinking and three days building will almost always outperform a team that spends five days building and then five more days rebuilding. The math is simple, but the discipline is hard.&lt;/p&gt;

&lt;h2 id="direction-comes-from-asking-better-questions"&gt;Direction Comes from Asking Better Questions&lt;/h2&gt;

&lt;p&gt;The fastest way to go in the right direction is to slow down at the beginning. Not permanently — just long enough to answer the questions that matter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;What problem are we actually solving?&lt;/strong&gt; Not the technical problem. The human one.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;How will we know it’s solved?&lt;/strong&gt; If you can’t define success, you can’t measure it.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;What’s the simplest thing that could work?&lt;/strong&gt; Complexity is easy. Simplicity requires understanding.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;What are we choosing &lt;em&gt;not&lt;/em&gt; to build?&lt;/strong&gt; Every yes is an implicit no to something else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren’t planning ceremonies or process overhead. They’re the cheapest investment you can make in avoiding expensive mistakes.&lt;/p&gt;

&lt;h2 id="speed-as-a-reward-not-a-goal"&gt;Speed as a Reward, Not a Goal&lt;/h2&gt;

&lt;p&gt;Speed should be the &lt;em&gt;result&lt;/em&gt; of good direction, not a substitute for it. When you deeply understand the problem, the constraints, and the desired outcome, fast execution follows naturally. You make fewer wrong turns. You write less throwaway code. You spend less time in meetings debating decisions that should have been made upfront.&lt;/p&gt;

&lt;p&gt;The teams I’ve seen ship the fastest over the long term aren’t the ones that type the fastest. They’re the ones that think clearly before they start typing. They &lt;a href="/blog/review-the-outcome-not-the-output"&gt;review the outcome, not the output&lt;/a&gt; — and that clarity is what keeps them pointed in the right direction.&lt;/p&gt;

&lt;h2 id="ai-makes-this-more-important-not-less"&gt;AI Makes This More Important, Not Less&lt;/h2&gt;

&lt;p&gt;AI tools make it trivially easy to generate code, scaffold systems, and spin up prototypes. That’s remarkable — and dangerous. When the cost of building drops to near zero, the cost of building the &lt;em&gt;wrong thing&lt;/em&gt; becomes the dominant expense.&lt;/p&gt;

&lt;p&gt;The bottleneck was never typing speed. It was always understanding — which is why &lt;a href="/blog/code-was-never-the-goal"&gt;code was never the goal&lt;/a&gt; in the first place, and why &lt;a href="/blog/dont-just-build-with-ai-learn-through-it"&gt;using AI to learn, not just build&lt;/a&gt;, pays off more than raw speed ever will. AI didn’t change that. It just made the consequences of poor understanding more visible, because now you can build the wrong thing in an afternoon instead of a quarter.&lt;/p&gt;

&lt;p&gt;Invest in direction first. Speed will follow.&lt;/p&gt;

&lt;hr&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Review the Outcome, Not the Output</title>
    <link rel="alternate" href="https://matthewboston.com/blog/review-the-outcome-not-the-output.html"/>
    <id>https://matthewboston.com/blog/review-the-outcome-not-the-output.html</id>
    <published>2026-03-26T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;In agentic engineering, humans don’t need to read every line of code anymore. What matters is whether the change solved the problem.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-old-review-model-is-breaking"&gt;The Old Review Model Is Breaking&lt;/h2&gt;

&lt;p&gt;Code review used to mean reading diffs line by line. You’d check variable names, spot off-by-one errors, argue about formatting, and verify that the logic matched the spec. This made sense when a human wrote every line. If a human chose it, a human should verify it.&lt;/p&gt;

&lt;p&gt;But when an AI agent writes the code, line-by-line review stops making sense. That doesn’t mean &lt;a href="/blog/code-quality-still-matters"&gt;quality stops mattering&lt;/a&gt; — it means the way we verify quality has to evolve. The agent didn’t choose that variable name because it was tired or because it has a bad habit. It followed a pattern. Nitpicking its syntax is like copy-editing a compiler’s output. You &lt;em&gt;can&lt;/em&gt; do it. You just shouldn’t.&lt;/p&gt;

&lt;h2 id="output-vs-outcome"&gt;Output vs. Outcome&lt;/h2&gt;

&lt;p&gt;There’s a distinction that matters here. The &lt;em&gt;output&lt;/em&gt; is the code — the diff, the lines changed, the files created. The &lt;em&gt;outcome&lt;/em&gt; is what changed in the system’s behavior. Did the bug get fixed? Does the feature work? Did performance improve? Is the user’s need met?&lt;/p&gt;

&lt;p&gt;When you review output, you’re asking “is this code correct?” When you review outcomes, you’re asking “did this change accomplish its goal?” The second question is harder, more valuable, and the one only a human can reliably answer.&lt;/p&gt;

&lt;p&gt;An agent can generate twenty different implementations that all pass the tests. Most of them are fine. Some are better than others. But whether the feature &lt;em&gt;should exist at all&lt;/em&gt; — whether it solves the right problem, fits the architecture, and serves the user — that’s judgment. That’s yours.&lt;/p&gt;

&lt;h2 id="what-outcome-review-looks-like"&gt;What Outcome Review Looks Like&lt;/h2&gt;

&lt;p&gt;Outcome review isn’t less rigorous than line-by-line review. It’s differently rigorous. Instead of scanning for syntax errors, you’re evaluating:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Intent alignment.&lt;/strong&gt; Does this change match what was actually requested? Agents are good at following instructions literally. They’re bad at questioning whether the instructions were right.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;System impact.&lt;/strong&gt; How does this change interact with the rest of the codebase? Does it introduce coupling that will hurt later? Does it respect existing boundaries?&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;User impact.&lt;/strong&gt; Does the end result actually improve the experience? A technically correct change that makes the product worse is still a bad change.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Test coverage.&lt;/strong&gt; Not “did the agent write tests” but “do these tests verify the thing that matters?” An agent will happily generate tests that pass without testing anything meaningful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the work that requires understanding the business, the users, and the system’s history. No agent has that context. You do.&lt;/p&gt;

&lt;h2 id="the-judgment-shift"&gt;The Judgment Shift&lt;/h2&gt;

&lt;p&gt;As AI handles more of the production work, the human role shifts from author to editor, from implementer to evaluator. This is the same shift I explored in &lt;a href="/blog/code-was-never-the-goal"&gt;Code Was Never the Goal&lt;/a&gt; — the craft was always about judgment, not keystrokes. This isn’t a demotion. Editing is harder than writing. Evaluating is harder than implementing. Knowing whether something &lt;em&gt;should&lt;/em&gt; be done requires more skill than knowing &lt;em&gt;how&lt;/em&gt; to do it.&lt;/p&gt;

&lt;p&gt;The engineers who thrive in agentic workflows won’t be the fastest typists or the ones who memorize the most APIs. They’ll be the ones with the best judgment — the ones who can look at a change and know whether it moves the system in the right direction. After all, &lt;a href="/blog/speed-is-only-useful-if-youre-going-in-the-right-direction"&gt;speed is only useful if you’re going in the right direction&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="let-go-of-the-diff"&gt;Let Go of the Diff&lt;/h2&gt;

&lt;p&gt;If you’re still reading every line of agent-generated code, you’re spending your attention on the wrong thing. You’re reviewing output when you should be reviewing outcomes. The code is a means to an end. Focus on the end.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Don't Hire Me</title>
    <link rel="alternate" href="https://matthewboston.com/blog/dont-hire-me.html"/>
    <id>https://matthewboston.com/blog/dont-hire-me.html</id>
    <published>2026-03-25T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Your expertise is a deployable artifact. A well-written SKILL.md file means anyone can spin up an AI agent that carries your judgment, your patterns, and your opinions into their codebase.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-old-model-is-one-to-one"&gt;The Old Model Is One-to-One&lt;/h2&gt;

&lt;p&gt;Traditional hiring is a bottleneck. You find a great engineer, negotiate a salary, onboard them for weeks, and then they work on one thing at a time. Their expertise lives in their head. When they leave, most of it leaves with them.&lt;/p&gt;

&lt;p&gt;This model made sense when the only way to apply expertise was to have the expert sit at a keyboard. But that constraint is dissolving.&lt;/p&gt;

&lt;h2 id="expertise-as-configuration"&gt;Expertise as Configuration&lt;/h2&gt;

&lt;p&gt;A SKILL.md file captures the things that make a specific engineer valuable: their architectural preferences, their patterns for handling edge cases, their opinions on testing and code quality, their domain knowledge. It’s not a resume. It’s an executable specification of how someone thinks about software.&lt;/p&gt;

&lt;p&gt;I wrote about this in &lt;a href="/blog/if-you-cant-one-shot-your-feature-its-a-skill-md-issue"&gt;If You Can’t One-Shot Your Feature, It’s a SKILL.md Issue&lt;/a&gt; — the real leverage in AI-assisted development comes from teaching the tools well. That same idea scales beyond your own projects. When you publish your SKILL.md, you’re making your expertise available to anyone who wants to spin up an agent that thinks the way you do.&lt;/p&gt;

&lt;h2 id="an-army-of-you"&gt;An Army of You&lt;/h2&gt;

&lt;p&gt;The provocative part isn’t the SKILL.md itself. It’s the multiplier. Once your expertise is encoded, it’s no longer bound to a single thread of execution. Someone can &lt;a href="/blog/turning-your-terminal-into-an-ai-dev-team"&gt;run multiple agents in parallel&lt;/a&gt;, each one carrying your patterns into a different part of the codebase. Your judgment scales horizontally.&lt;/p&gt;

&lt;p&gt;This isn’t science fiction. It’s the natural extension of what happens when you combine good context engineering with AI agents that can act on it. The bottleneck stops being “how many hours does this person have” and becomes “how well have they encoded what they know.”&lt;/p&gt;

&lt;h2 id="what-youre-really-selling"&gt;What You’re Really Selling&lt;/h2&gt;

&lt;p&gt;If &lt;a href="/blog/code-was-never-the-goal"&gt;code was never the goal&lt;/a&gt;, then selling your time at a keyboard was always a rough proxy for what you actually provide: judgment. The ability to make the right call on architecture, testing strategy, error handling, and tradeoffs.&lt;/p&gt;

&lt;p&gt;A SKILL.md makes that judgment portable. It separates what you know from where you happen to be sitting. An engineer with deep expertise in resilience patterns and platform tooling doesn’t need to join your team full-time to influence how your systems get built. They just need to publish a good SKILL.md.&lt;/p&gt;

&lt;h2 id="the-catch"&gt;The Catch&lt;/h2&gt;

&lt;p&gt;This only works if the SKILL.md is good. A vague, generic skill file produces vague, generic output. The same way a bad hire is expensive, a bad skill file wastes tokens and produces code that has to be thrown away. The engineers who invest in encoding their expertise well will have outsized impact — not because they write more code, but because their thinking runs everywhere.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Don’t hire me. Spin up as many instances of &lt;a href="https://matthewboston.com/SKILL.md"&gt;me&lt;/a&gt; as you’d like.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7442683217012408320/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Use AI Agents to Break Your App</title>
    <link rel="alternate" href="https://matthewboston.com/blog/use-ai-agents-to-break-your-app.html"/>
    <id>https://matthewboston.com/blog/use-ai-agents-to-break-your-app.html</id>
    <published>2026-03-21T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Use AI agents to break your app in ways your scripts won’t.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="beyond-the-happy-path"&gt;Beyond the Happy Path&lt;/h2&gt;

&lt;p&gt;Most test suites walk the happy path. Login, click a button, verify a response. Maybe there’s a sad path test for an invalid email. But that’s a narrow slice of what real users do. Real users take detours. They double-click things, navigate backwards, paste garbage into fields, and use your app in sequences you never imagined.&lt;/p&gt;

&lt;p&gt;Traditional test scripts can’t model this kind of chaos because someone has to think of every scenario in advance. And if you could think of it, you’d probably already have a test for it.&lt;/p&gt;

&lt;h2 id="what-ai-agents-do-differently"&gt;What AI Agents Do Differently&lt;/h2&gt;

&lt;p&gt;AI agents can roam your application semi-randomly, blending weird inputs and chaining flows together to hit edge cases you never modeled. They don’t follow a script — they explore. They click things in unexpected orders, fill in forms with creative garbage, and navigate paths that no human tester would think to try.&lt;/p&gt;

&lt;p&gt;This isn’t fuzzing. Fuzzing throws random bytes at an interface. AI agents understand the structure of your application well enough to generate traffic that looks like real users behaving unpredictably. They know what a form is, what a button does, and what a valid-but-unusual input looks like. That’s the difference between noise and useful chaos.&lt;/p&gt;

&lt;h2 id="synthetic-testing-on-steroids"&gt;Synthetic Testing on Steroids&lt;/h2&gt;

&lt;p&gt;Think of it as synthetic testing with a brain. Instead of replaying the same five user journeys on a loop, you have an agent that generates continuous, chaotic traffic — traffic that still looks realistic enough to exercise real code paths. It finds the “impossible” bugs: the race condition that only triggers when two specific actions happen within the same 200ms window, the state corruption that requires a precise sequence of navigation and form submission, the memory leak that only shows up after a particular pattern of page transitions.&lt;/p&gt;

&lt;p&gt;These are bugs that scripted tests will never catch because no one would think to write a test for them. They’re the bugs your users find in production on a Friday afternoon.&lt;/p&gt;

&lt;h2 id="making-it-practical"&gt;Making It Practical&lt;/h2&gt;

&lt;p&gt;Plug agent-driven testing into CI or a synthetic environment with solid observability and you catch these bugs before your users ever see them. The key ingredients:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;A realistic environment&lt;/strong&gt; where agents can roam without affecting production data&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Good observability&lt;/strong&gt; so you can trace what the agent did when something breaks&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Structured chaos&lt;/strong&gt; — agents should explore broadly but still generate meaningful interactions&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Automated triage&lt;/strong&gt; to separate real bugs from expected failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need to replace your existing test suite. Agent-driven testing is a layer on top. Your unit tests verify correctness. Your integration tests verify contracts. Your AI agents verify that your app survives contact with the real world.&lt;/p&gt;

&lt;h2 id="the-shift-in-testing-philosophy"&gt;The Shift in Testing Philosophy&lt;/h2&gt;

&lt;p&gt;The traditional approach to testing is prescriptive: define what should happen, verify it does. Agent-driven testing is exploratory: turn something loose on your app and see what breaks. Both are valuable. But as applications grow more complex and user behavior grows more unpredictable, the exploratory approach catches the class of bugs that prescriptive testing systematically misses.&lt;/p&gt;

&lt;p&gt;Your test suite tells you the app works as designed. AI agents tell you whether it survives as deployed.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>If You Can't One-Shot Your Feature, It's a SKILL.md Issue</title>
    <link rel="alternate" href="https://matthewboston.com/blog/if-you-cant-one-shot-your-feature-its-a-skill-md-issue.html"/>
    <id>https://matthewboston.com/blog/if-you-cant-one-shot-your-feature-its-a-skill-md-issue.html</id>
    <published>2026-03-14T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;The more I use AI-assisted tools, the more I realize the real work isn’t writing features faster — it’s teaching the tools better.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="weve-always-been-toolsmiths"&gt;We’ve Always Been Toolsmiths&lt;/h2&gt;

&lt;p&gt;Software engineers have always customized their environments. We tweak our terminals, configure our editors, remap our keyboards. We write shell aliases, create snippets, and build custom linters. The goal has always been the same: reduce friction between intent and execution.&lt;/p&gt;

&lt;p&gt;AI-assisted development is just the next layer of that same tinkering. Instead of writing a vim macro, you’re writing a SKILL.md file that teaches your AI agent how to approach a specific type of task. Instead of configuring a linter rule, you’re describing the patterns and conventions your codebase follows so the agent can match them.&lt;/p&gt;

&lt;h2 id="what-a-skillmd-actually-does"&gt;What a SKILL.md Actually Does&lt;/h2&gt;

&lt;p&gt;A SKILL.md file is context that persists across sessions. It captures the patterns, conventions, and domain knowledge that make the difference between an AI agent that writes generic code and one that writes code that fits your project.&lt;/p&gt;

&lt;p&gt;When you can’t one-shot a feature — when the agent keeps getting it wrong, or you have to correct the same mistakes repeatedly — that’s a signal. The agent isn’t dumb; it’s under-informed. The fix isn’t to type harder. The fix is to improve the context you’re feeding it. This is the same insight behind the Research-Plan-Implement workflow I described in &lt;a href="/blog/a-bad-line-of-research-is-a-hundred-bad-lines-of-code"&gt;A Bad Line of Research Is a Hundred Bad Lines of Code&lt;/a&gt; — bad context in means bad code out.&lt;/p&gt;

&lt;h2 id="the-feedback-loop"&gt;The Feedback Loop&lt;/h2&gt;

&lt;p&gt;Here’s the cycle I follow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
&lt;strong&gt;Attempt the feature.&lt;/strong&gt; Let the agent take its best shot.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Identify the gaps.&lt;/strong&gt; Where did it go wrong? Missing conventions? Wrong patterns? Incomplete understanding of the domain?&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Capture the knowledge.&lt;/strong&gt; Write it down in a SKILL.md or CLAUDE.md file so the agent has it next time.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Try again.&lt;/strong&gt; The next attempt should be closer. If not, repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Over time, your SKILL.md files accumulate the tribal knowledge of your project. They become a living document of how your codebase works, written in a format that both humans and AI agents can use.&lt;/p&gt;

&lt;h2 id="the-better-your-skills-the-further-you-can-push"&gt;The Better Your Skills, the Further You Can Push&lt;/h2&gt;

&lt;p&gt;This is the real leverage. Every hour spent improving your SKILL.md files pays compound interest. A well-documented set of skills means you can tackle increasingly complex features with higher first-attempt success rates. Your agents get smarter not because the models improve, but because the context you provide improves. And the impact doesn’t stop at your own projects — a published SKILL.md means &lt;a href="/blog/dont-hire-me"&gt;anyone can spin up instances of your expertise&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The engineers who will get the most out of AI tooling aren’t the ones who prompt the hardest. They’re the ones who invest in teaching their tools well.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Turning Your Terminal Into an AI Dev Team</title>
    <link rel="alternate" href="https://matthewboston.com/blog/turning-your-terminal-into-an-ai-dev-team.html"/>
    <id>https://matthewboston.com/blog/turning-your-terminal-into-an-ai-dev-team.html</id>
    <published>2026-03-13T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;I’ve been using Claude Code with agent teams in a tmux terminal, and it fundamentally changes the development workflow.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-setup"&gt;The Setup&lt;/h2&gt;

&lt;p&gt;&lt;img src="/images/ai-dev-team-tmux.png" alt="Claude Code agents running in parallel across tmux panes, each fixing a different security issue"&gt;&lt;/p&gt;

&lt;p&gt;Each agent gets its own tmux pane. One might be running tests, another opening branches, and a third reporting status — all in real time. I stay in one session, steering and reviewing, while the agents grind through new features, security fixes, and refactors.&lt;/p&gt;

&lt;p&gt;It’s the first AI dev setup that feels like running a whole team instead of a single slow bot.&lt;/p&gt;

&lt;h2 id="why-tmux-changes-everything"&gt;Why Tmux Changes Everything&lt;/h2&gt;

&lt;p&gt;Most AI coding tools give you a single thread of execution. You ask for something, wait, review, ask again. It’s sequential and slow. But with tmux panes, you get genuine parallelism. While one agent is refactoring a module, another can be writing tests for a different part of the codebase. A third can be running security audits.&lt;/p&gt;

&lt;p&gt;The key insight is that many development tasks are independent. Feature work on module A doesn’t block test improvements on module B. By giving each task its own agent in its own pane, you remove the artificial bottleneck of single-threaded AI assistance. Take this further and the agents don’t even need to carry &lt;em&gt;your&lt;/em&gt; patterns — they can carry &lt;a href="/blog/dont-hire-me"&gt;anyone’s encoded expertise&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="the-human-role-shifts"&gt;The Human Role Shifts&lt;/h2&gt;

&lt;p&gt;With this setup, my role shifts from writing code to something closer to a tech lead running a team. I’m reviewing pull requests, making architectural decisions, and ensuring the agents stay aligned with the actual goal. The tedious parts — writing boilerplate, running through test matrices, updating documentation — get handled by agents that don’t get bored or distracted.&lt;/p&gt;

&lt;p&gt;This doesn’t replace engineering judgment. It amplifies it. I spend more time on the decisions that matter and less time on the mechanical work that doesn’t. The temptation is to review every line of agent-generated code, but that’s the wrong use of your attention — &lt;a href="/blog/review-the-outcome-not-the-output"&gt;review the outcome, not the output&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="getting-started"&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;If you want to try this yourself, start simple: two tmux panes, one for your main agent and one for a test runner. Make sure your &lt;a href="/blog/coding-agents-need-a-faster-feedback-loop"&gt;feedback loop is fast enough&lt;/a&gt; to keep those agents productive — a slow test suite bottlenecks every pane. Get comfortable with the workflow before scaling up. The coordination overhead is real — more agents means more context to track. But once you find your rhythm, it’s hard to go back to single-threaded AI assistance.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Coding Agents Need a Faster Feedback Loop</title>
    <link rel="alternate" href="https://matthewboston.com/blog/coding-agents-need-a-faster-feedback-loop.html"/>
    <id>https://matthewboston.com/blog/coding-agents-need-a-faster-feedback-loop.html</id>
    <published>2026-03-06T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;If coding agents are going to be useful, they need the same thing humans do — fast feedback.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-foundation-of-autonomous-coding"&gt;The Foundation of Autonomous Coding&lt;/h2&gt;

&lt;p&gt;That means linting, formatting, and tests must run locally and complete quickly. If a single test run takes minutes, the agent can’t safely explore or validate changes. The faster the loop, the smarter and safer the agent becomes.&lt;/p&gt;

&lt;p&gt;Speed isn’t a luxury here — it’s the foundation for autonomous coding to work.&lt;/p&gt;

&lt;h2 id="why-this-matters-more-for-agents-than-humans"&gt;Why This Matters More for Agents Than Humans&lt;/h2&gt;

&lt;p&gt;Humans can context-switch while tests run. We check email, review a PR, grab coffee. An AI agent doesn’t have that luxury. It’s sitting there, burning tokens and time, waiting for a result before it can take the next step. A five-minute test suite means five minutes of dead time per iteration. Over a complex feature with dozens of iterations, that adds up to hours of wasted compute.&lt;/p&gt;

&lt;p&gt;But there’s a deeper problem. Agents make mistakes. That’s expected and fine — as long as they can detect and correct those mistakes quickly. A fast feedback loop means the agent catches a broken test within seconds, fixes it, and moves on. A slow feedback loop means the agent has already written three more files on top of a broken foundation before it discovers anything went wrong.&lt;/p&gt;

&lt;h2 id="what-fast-looks-like"&gt;What Fast Looks Like&lt;/h2&gt;

&lt;p&gt;For effective agent-driven development, aim for these targets:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Linting:&lt;/strong&gt; Under 5 seconds for the changed files&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Formatting:&lt;/strong&gt; Under 2 seconds&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Unit tests:&lt;/strong&gt; Under 30 seconds for the relevant test files&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Type checking:&lt;/strong&gt; Under 10 seconds for incremental checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your full test suite takes 10 minutes, that’s fine for CI. But you need a way for the agent to run just the relevant tests in seconds. Tools like test file matching, watch mode, and incremental compilation make this possible. And &lt;a href="/blog/your-tests-that-make-network-requests-are-flaky"&gt;keep network requests out of your core test suite&lt;/a&gt; — flaky tests that depend on external services are the worst kind of slow feedback.&lt;/p&gt;

&lt;h2 id="the-investment-pays-off"&gt;The Investment Pays Off&lt;/h2&gt;

&lt;p&gt;Every minute you spend optimizing your local feedback loop doesn’t just help you — it helps every agent session that follows. A fast test suite is a force multiplier. It makes your agents braver, because they can afford to experiment. It makes them safer, because they catch problems early. And it makes them faster, because they spend less time waiting and more time building.&lt;/p&gt;

&lt;p&gt;If you’re investing in AI-assisted development, invest in your feedback loop first. Everything else depends on it.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Every CLI Should Include Agent Skills</title>
    <link rel="alternate" href="https://matthewboston.com/blog/every-cli-should-include-agent-skills.html"/>
    <id>https://matthewboston.com/blog/every-cli-should-include-agent-skills.html</id>
    <published>2026-02-27T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Most command-line tools still work like it’s 1995. But what if your CLI could think?&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-current-state-of-clis"&gt;The Current State of CLIs&lt;/h2&gt;

&lt;p&gt;Command-line tools are powerful but rigid. They do exactly what you tell them, nothing more. When something goes wrong, you get an error code and maybe a cryptic message. Figuring out what happened and what to do next is entirely on you.&lt;/p&gt;

&lt;p&gt;This works fine when you know exactly what you’re doing. But how often are you reaching for Stack Overflow or documentation to figure out the right flags, the correct sequence of commands, or why something failed?&lt;/p&gt;

&lt;h2 id="imagine-smarter-tools"&gt;Imagine Smarter Tools&lt;/h2&gt;

&lt;p&gt;Imagine a &lt;code&gt;git&lt;/code&gt; that suggests the right command after a messy merge — not just showing you conflict markers, but explaining what happened and offering resolution strategies based on the actual content of the conflicts. I already built a small version of this with &lt;a href="/blog/i-got-tired-of-writing-bad-commit-messages"&gt;git ai-commit&lt;/a&gt; — a CLI that reads the staged diff and writes a conventional commit message. It’s a glimpse of what every tool could become.&lt;/p&gt;

&lt;p&gt;Imagine a &lt;code&gt;kubectl&lt;/code&gt; that explains why your pod keeps crashing before you even ask — correlating the error logs, the resource limits, and the recent config changes to give you a diagnosis instead of a wall of YAML.&lt;/p&gt;

&lt;p&gt;Imagine a &lt;code&gt;docker&lt;/code&gt; that notices you’re building an image with a known vulnerability in the base layer and suggests an alternative before you push it to production.&lt;/p&gt;

&lt;h2 id="what-agent-skills-look-like"&gt;What Agent Skills Look Like&lt;/h2&gt;

&lt;p&gt;By adding lightweight agent skills to CLIs — context awareness, reasoning, and adaptive help — these tools could evolve from rigid executors into true collaborators. The key components are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Context awareness:&lt;/strong&gt; Understanding not just the command, but the state of the project, recent changes, and the developer’s likely intent&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Reasoning:&lt;/strong&gt; Connecting symptoms to causes, suggesting next steps, and explaining trade-offs&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Adaptive help:&lt;/strong&gt; Providing guidance that’s specific to your situation, not generic documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This doesn’t mean every CLI needs a full language model embedded in it. It means CLIs should expose the right hooks — structured output, state inspection, error context — so that AI layers can reason about them effectively.&lt;/p&gt;

&lt;h2 id="the-path-forward"&gt;The Path Forward&lt;/h2&gt;

&lt;p&gt;The tools that adopt this first will have a massive advantage. Developers will gravitate toward CLIs that help them think, not just execute. The ones that stay rigid will feel increasingly primitive by comparison.&lt;/p&gt;

&lt;p&gt;We’ve always shaped our tools to fit how we work. Now our tools can start shaping themselves.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>A Bad Line of Research Is a Hundred Bad Lines of Code</title>
    <link rel="alternate" href="https://matthewboston.com/blog/a-bad-line-of-research-is-a-hundred-bad-lines-of-code.html"/>
    <id>https://matthewboston.com/blog/a-bad-line-of-research-is-a-hundred-bad-lines-of-code.html</id>
    <published>2026-02-20T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Dex Horthy’s &lt;a href="https://www.youtube.com/watch?v=rmvDxxNubIg"&gt;“No Vibes Allowed”&lt;/a&gt; talk nailed it: AI agents are stateless. The only way to get better output is better context in.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-problem-with-vibes"&gt;The Problem with Vibes&lt;/h2&gt;

&lt;p&gt;When developers first start using AI coding agents, the natural approach is to describe what you want and hope for the best. Vibes-based development. Sometimes it works beautifully. Other times you get code that looks right but misses the architectural patterns, naming conventions, or business logic that your codebase depends on.&lt;/p&gt;

&lt;p&gt;The issue isn’t the model’s capability. It’s the context window. An agent that doesn’t understand your codebase will write generic solutions. An agent loaded with the right context will write code that fits. This is why &lt;a href="/blog/stay-out-of-the-dumb-zone"&gt;context window management&lt;/a&gt; is the skill nobody talks about — the quality of what’s in the window matters as much as the size.&lt;/p&gt;

&lt;h2 id="research-plan-implement"&gt;Research-Plan-Implement&lt;/h2&gt;

&lt;p&gt;Dex’s &lt;a href="https://www.youtube.com/watch?v=rmvDxxNubIg"&gt;Research-Plan-Implement&lt;/a&gt; (RPI) workflow keeps agents in the smart zone. The discipline is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research&lt;/strong&gt; the codebase first. Read the code, trace the dependencies, understand the patterns already in use. Don’t assume — verify. Look at how similar features were built before. Check the test patterns. Read the configuration. This phase isn’t just about feeding context to the agent — it’s about &lt;a href="/blog/dont-just-build-with-ai-learn-through-it"&gt;building your own understanding&lt;/a&gt; of the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan&lt;/strong&gt; the changes next. Write down the approach, the files involved, the expected outcome. This isn’t bureaucracy — it’s context loading. The plan becomes part of the agent’s working memory, keeping it aligned as implementation gets complex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implement&lt;/strong&gt; only after research and planning are loaded into the context window. By this point, the agent has everything it needs to write code that actually fits.&lt;/p&gt;

&lt;h2 id="why-this-matters"&gt;Why This Matters&lt;/h2&gt;

&lt;p&gt;A bad line of research cascades. If the agent misunderstands the architecture, every file it touches will reflect that misunderstanding. If it misses a naming convention, every function it writes will be inconsistent. If it doesn’t know about an existing utility, it’ll reinvent it poorly.&lt;/p&gt;

&lt;p&gt;But a good line of research compounds. Understanding the existing patterns means the new code slots in naturally. Knowing the test conventions means tests get written correctly the first time. Reading the configuration means no surprises at deploy time.&lt;/p&gt;

&lt;h2 id="building-the-discipline"&gt;Building the Discipline&lt;/h2&gt;

&lt;p&gt;This is what inspired me to build &lt;a href="https://github.com/bostonaholic/rpikit"&gt;rpikit&lt;/a&gt;, a tool that enforces this discipline at every phase. Verify before claiming done. Evaluate feedback before accepting it. Don’t outsource the thinking — outsource the typing.&lt;/p&gt;

&lt;p&gt;The engineers getting the best results from AI aren’t the ones who prompt the hardest. They’re the ones who research the deepest before they start — and the ones who &lt;a href="/blog/if-you-cant-one-shot-your-feature-its-a-skill-md-issue"&gt;capture what they learn in SKILL.md files&lt;/a&gt; so the next session starts smarter than the last.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>AI Never Would Have Installed left-pad</title>
    <link rel="alternate" href="https://matthewboston.com/blog/ai-never-would-have-installed-left-pad.html"/>
    <id>https://matthewboston.com/blog/ai-never-would-have-installed-left-pad.html</id>
    <published>2026-01-20T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;AI never would have installed left-pad. It would have written its own in five lines, added three tests, and moved on.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-left-pad-lesson-revisited"&gt;The left-pad Lesson, Revisited&lt;/h2&gt;

&lt;p&gt;In 2016, a developer unpublished an 11-line npm package called left-pad, and half the JavaScript ecosystem broke. It exposed a real problem: the industry had developed a habit of importing trivial packages instead of writing trivial code.&lt;/p&gt;

&lt;p&gt;At the time, the justification made sense. Why write something yourself when a tested, maintained package exists? The cost of writing it was low, but the cost of maintaining it — keeping up with edge cases, writing tests, handling bug reports — was ongoing.&lt;/p&gt;

&lt;h2 id="what-changes-with-ai"&gt;What Changes with AI&lt;/h2&gt;

&lt;p&gt;AI fundamentally shifts this calculus. The cost of writing trivial utility functions is now effectively zero. An AI agent can write a left-pad function, add comprehensive tests, and move on in seconds. There’s no maintenance burden because there’s no external dependency. There’s no supply chain risk because the code lives in your repo. There’s no version compatibility issue because you control every line.&lt;/p&gt;

&lt;p&gt;This doesn’t mean we should stop using packages entirely. Complex libraries that encapsulate genuine domain expertise — cryptography, date handling, HTTP clients — still earn their place in your dependency tree. The expertise required to get those right justifies the dependency.&lt;/p&gt;

&lt;p&gt;But for simple utilities? The argument for installing a package is much weaker now.&lt;/p&gt;

&lt;h2 id="rethinking-the-dependency-tree"&gt;Rethinking the Dependency Tree&lt;/h2&gt;

&lt;p&gt;Every dependency you add is a bet. You’re betting that the maintainer will keep it updated, that it won’t introduce breaking changes at an inconvenient time, and that its transitive dependencies won’t create conflicts. For complex packages, that bet is usually worth it. For trivial ones, it often isn’t.&lt;/p&gt;

&lt;p&gt;AI gives us a way to stop making that bet for code we could easily own ourselves. Write it, test it, own it. Your supply chain gets smaller, your builds get faster, and your risk surface shrinks.&lt;/p&gt;

&lt;h2 id="the-takeaway"&gt;The Takeaway&lt;/h2&gt;

&lt;p&gt;The next time you reach for a tiny utility package, ask yourself: would AI just write this? If the answer is yes, maybe you should let it. Five lines of owned code beats one line in your &lt;code&gt;package.json&lt;/code&gt; that points to someone else’s weekend project.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Code Was Never the Goal</title>
    <link rel="alternate" href="https://matthewboston.com/blog/code-was-never-the-goal.html"/>
    <id>https://matthewboston.com/blog/code-was-never-the-goal.html</id>
    <published>2026-01-19T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;I love writing code. But the point has always been to solve real problems for real users.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-shift"&gt;The Shift&lt;/h2&gt;

&lt;p&gt;AI just sped that up. It takes care of the boring parts so engineers can spend more time on what actually matters: designing systems, thinking about tradeoffs, and making the decisions that really change outcomes for users.&lt;/p&gt;

&lt;p&gt;This feels threatening if you define yourself by the act of typing code. It feels liberating if you define yourself by the problems you solve.&lt;/p&gt;

&lt;h2 id="what-the-craft-actually-is"&gt;What the Craft Actually Is&lt;/h2&gt;

&lt;p&gt;The craft of programming isn’t disappearing. It’s shifting from typing code to understanding problems, shaping architectures, and knowing which of the many possible solutions is the right one to build.&lt;/p&gt;

&lt;p&gt;Consider what makes a senior engineer valuable. It’s rarely their typing speed or their ability to write a sorting algorithm from memory. It’s their judgment. &lt;a href="/blog/speed-is-only-useful-if-youre-going-in-the-right-direction"&gt;Speed without direction is just expensive wandering&lt;/a&gt;. They know which problems to solve and which to ignore. They know when a simple solution will suffice and when complexity is warranted. They know how to design systems that survive contact with reality.&lt;/p&gt;

&lt;p&gt;None of that changes with AI. If anything, it becomes more important. When generating code is cheap, the decisions about what code to generate become the bottleneck. The engineer who understands the domain, the users, and the constraints is more valuable than ever.&lt;/p&gt;

&lt;h2 id="the-parts-that-stay"&gt;The Parts That Stay&lt;/h2&gt;

&lt;p&gt;Some things don’t change no matter who — or what — writes the code:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Systems thinking&lt;/strong&gt; still matters. Understanding how components interact, where bottlenecks will emerge, and how failure modes cascade is irreplaceable.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Domain knowledge&lt;/strong&gt; still matters. Knowing your users, your business constraints, and your regulatory environment can’t be prompted into existence.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Taste&lt;/strong&gt; still matters. Choosing the right abstraction, the right level of complexity, the right tradeoff between speed and correctness — that’s engineering judgment, not code generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="embracing-the-shift"&gt;Embracing the Shift&lt;/h2&gt;

&lt;p&gt;The engineers who thrive in this new landscape will be the ones who stop measuring their productivity in lines of code and start measuring it in problems solved — who &lt;a href="/blog/review-the-outcome-not-the-output"&gt;review the outcome, not the output&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Code was never the goal. It was always just the medium. And now we have a faster way to work in that medium.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Vibe-Coded Apps Need Real Security</title>
    <link rel="alternate" href="https://matthewboston.com/blog/vibe-coded-apps-need-real-security.html"/>
    <id>https://matthewboston.com/blog/vibe-coded-apps-need-real-security.html</id>
    <published>2026-01-18T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Earlier this week a vibe-coded Google Calendar app went semi-viral. I was excited about it, but also worried about how loose most of these projects are with security.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-problem-i-found"&gt;The Problem I Found&lt;/h2&gt;

&lt;p&gt;The app was a vibe-coded Google Calendar tool called &lt;a href="https://github.com/gabrielvaldivia/big-year"&gt;big-year&lt;/a&gt; that was picking up traction fast. I was genuinely excited about it — the concept was great. But I’ve seen enough rushed projects to know that security is usually the first thing sacrificed for speed.&lt;/p&gt;

&lt;p&gt;I pointed my Claude Code &lt;a href="https://github.com/bostonaholic/dotfiles/blob/main/claude/commands/security-audit.md"&gt;security audit command&lt;/a&gt; and &lt;a href="https://github.com/bostonaholic/dotfiles/blob/main/claude/agents/security-privacy-auditor.md"&gt;security agent&lt;/a&gt; at the repo, and in minutes they found a critical issue: the author had committed Google auth tokens for three of their own accounts, and anyone using the web version could have had their own data exposed.&lt;/p&gt;

&lt;p&gt;The bad news is open source made those tokens copy-paste ready. The good news is open source also let me find the problem fast, &lt;a href="https://github.com/gabrielvaldivia/big-year/pull/2"&gt;open a PR&lt;/a&gt;, and DM the author before things got worse.&lt;/p&gt;

&lt;h2 id="why-vibe-coding-creates-security-gaps"&gt;Why Vibe Coding Creates Security Gaps&lt;/h2&gt;

&lt;p&gt;Vibe coding — using AI to rapidly generate applications through natural language prompts — optimizes for speed and functionality. Security is rarely part of the vibe. I saw the same pattern from the other side in &lt;a href="/blog/my-ai-recruiter-honeypot-worked"&gt;My AI Recruiter Honeypot Worked&lt;/a&gt; — the bots generating recruiter outreach had zero guardrails against adversarial input. When you’re iterating quickly, asking an AI to build features as fast as possible, the generated code tends to take the shortest path. That shortest path often means hardcoded credentials, missing input validation, and overly permissive configurations.&lt;/p&gt;

&lt;p&gt;The developer in the flow state of vibe coding isn’t thinking about secrets management or OWASP top 10. They’re thinking about getting the feature to work. And the AI, optimizing for the prompt it was given, happily generates working but insecure code.&lt;/p&gt;

&lt;h2 id="what-we-can-do-about-it"&gt;What We Can Do About It&lt;/h2&gt;

&lt;p&gt;The solution isn’t to stop vibe coding. It’s to add security guardrails that work at the same speed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Pre-commit hooks&lt;/strong&gt; that scan for secrets before they ever hit the repo. Tools like &lt;a href="https://github.com/awslabs/git-secrets"&gt;git-secrets&lt;/a&gt; or &lt;a href="https://github.com/gitleaks/gitleaks"&gt;gitleaks&lt;/a&gt; catch credentials before they become public.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;AI security scans&lt;/strong&gt; that can audit a codebase in minutes. If it took me minutes to find the issue with Claude Code, it should be part of every vibe coder’s workflow.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Template security.&lt;/strong&gt; If frameworks and starter templates ship with secure defaults — environment variables for secrets, CORS configured, input sanitization in place — then the vibe-coded app starts from a safer baseline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="the-responsibility-gap"&gt;The Responsibility Gap&lt;/h2&gt;

&lt;p&gt;When anyone can build an app in an afternoon, we need to make sure security knowledge isn’t a prerequisite for shipping. It needs to be baked into the tools. This is exactly what &lt;a href="/blog/building-a-platform-is-raising-the-floor-for-everyone"&gt;platform engineering does&lt;/a&gt; — it raises the floor so that security and quality don’t depend on individual expertise. The alternative is an explosion of deployed apps with the same classes of vulnerabilities we’ve been fighting for decades, just generated faster.&lt;/p&gt;

&lt;p&gt;Security at the speed of vibes — that’s the real challenge.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Code Quality Still Matters</title>
    <link rel="alternate" href="https://matthewboston.com/blog/code-quality-still-matters.html"/>
    <id>https://matthewboston.com/blog/code-quality-still-matters.html</id>
    <published>2026-01-17T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;AI writes almost all my code now. It’s fast, confident, and occasionally wrong in subtle ways.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-temptation-to-skip-the-basics"&gt;The Temptation to Skip the Basics&lt;/h2&gt;

&lt;p&gt;Just because a model wrote it doesn’t mean we can skip the basics. There’s a temptation — when code appears instantly and looks reasonable — to trust it and move on. The speed creates a false sense of confidence. But AI-generated code has the same failure modes as human-written code: it can be unclear, overly complex, poorly structured, and hard to maintain.&lt;/p&gt;

&lt;p&gt;The difference is volume. AI generates code faster, which means it can generate bad code faster too. Without quality checks, you accumulate technical debt at machine speed. This is the same trap I wrote about in &lt;a href="/blog/ship-as-fast-as-possible-but-not-faster"&gt;Ship as Fast as Possible, but Not Faster&lt;/a&gt; — the dynamic doesn’t change just because AI is holding the keyboard.&lt;/p&gt;

&lt;h2 id="first-principles-havent-changed"&gt;First Principles Haven’t Changed&lt;/h2&gt;

&lt;p&gt;We still need to care about clarity, simplicity, and design. These aren’t nostalgic preferences — they’re engineering requirements that exist for practical reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Clarity&lt;/strong&gt; because someone (or some agent) will need to understand this code later – and &lt;a href="/blog/sparse-is-better-than-dense"&gt;dense code&lt;/a&gt; and &lt;a href="/blog/explicit-is-better-than-implicit"&gt;implicit decisions&lt;/a&gt; make that harder than it needs to be&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt; because every unnecessary abstraction is a future maintenance burden – &lt;a href="/blog/simple-is-better-than-complex"&gt;simple beats complex&lt;/a&gt;, and &lt;a href="/blog/flat-is-better-than-nested"&gt;flat structures beat nested ones&lt;/a&gt; every time&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Design&lt;/strong&gt; because the structure of your code determines how easily it can change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad code — AI or human — still rots the same way. Unclear naming leads to misunderstandings. Tight coupling makes changes risky. Missing tests mean bugs hide longer. The source of the code doesn’t change any of these dynamics.&lt;/p&gt;

&lt;h2 id="what-quality-looks-like-now"&gt;What Quality Looks Like Now&lt;/h2&gt;

&lt;p&gt;Code review matters more, not less. When AI generates a hundred lines in seconds, the human reviewing those lines is the quality gate. That review needs to be thorough:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Does this code actually solve the right problem?&lt;/li&gt;
  &lt;li&gt;Is it simpler than it needs to be, or more complex?&lt;/li&gt;
  &lt;li&gt;Does it follow the patterns established in this codebase?&lt;/li&gt;
  &lt;li&gt;Are the edge cases handled?&lt;/li&gt;
  &lt;li&gt;Will someone understand this in six months?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is knowing where to focus your attention. Nitpicking syntax in agent output wastes your judgment — &lt;a href="/blog/review-the-outcome-not-the-output"&gt;review the outcome, not the output&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The medium changed, not the craft. Write it fast, review it carefully, and maintain it thoughtfully. That’s always been the job.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>My AI Recruiter Honeypot Worked</title>
    <link rel="alternate" href="https://matthewboston.com/blog/my-ai-recruiter-honeypot-worked.html"/>
    <id>https://matthewboston.com/blog/my-ai-recruiter-honeypot-worked.html</id>
    <published>2026-01-16T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;I slipped a prompt injection into my LinkedIn “About” section to see which recruiters were actually bots. The results were both hilarious and revealing.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-setup"&gt;The Setup&lt;/h2&gt;

&lt;p&gt;The idea was simple: embed a subtle instruction in my LinkedIn profile that a human would skip right over but an AI would follow. If a recruiter’s message showed signs of following the injected instructions, I’d know they were using an AI agent to send outreach.&lt;/p&gt;

&lt;p&gt;Think of it as a honeypot, but for automated recruiting bots instead of network attackers.&lt;/p&gt;

&lt;h2 id="what-happened"&gt;What Happened&lt;/h2&gt;

&lt;p&gt;The first message I got followed my injected instructions exactly. A week later, the same “recruiter” messaged me again, this time opening with “Hi David.” My name is not David. The bot was clearly pulling from a template and failing basic personalization while simultaneously being susceptible to prompt injection.&lt;/p&gt;

&lt;p&gt;It turned into a fun little experiment. I started tweaking the injection to see what else these AI-powered recruiters would do and what I could learn from their behavior.&lt;/p&gt;

&lt;h2 id="what-this-tells-us"&gt;What This Tells Us&lt;/h2&gt;

&lt;p&gt;The experiment revealed a few things about the current state of AI-assisted recruiting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most automated outreach is poorly built.&lt;/strong&gt; The bots that fell for a basic prompt injection weren’t sophisticated. They were simple scrape-and-generate pipelines with no guardrails against adversarial input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The human touch is easy to fake but hard to replicate.&lt;/strong&gt; A message that says “I noticed your work on X” sounds personal, but when it’s generated from profile keywords by a bot that also calls you the wrong name, the illusion falls apart quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt injection is everywhere.&lt;/strong&gt; My LinkedIn profile is a text field that gets fed into AI systems I don’t control. That’s true of almost every public-facing text on the internet now. Any text that might be consumed by an AI model is a potential injection surface.&lt;/p&gt;

&lt;h2 id="the-bigger-picture"&gt;The Bigger Picture&lt;/h2&gt;

&lt;p&gt;This was a lighthearted experiment, but it points to a serious issue. As more business communication gets automated through AI, the attack surface for prompt injection expands. Your email signature, your LinkedIn summary, your GitHub bio — any of these could be crafted to influence AI systems that process them. The same blind spot shows up in code generation — &lt;a href="/blog/vibe-coded-apps-need-real-security"&gt;vibe-coded apps routinely ship with committed credentials&lt;/a&gt; because security wasn’t part of the vibe.&lt;/p&gt;

&lt;p&gt;Defenders need to think about this. And recruiters — the real ones — might want to double-check what their tools are actually sending.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Stay Out of the Dumb Zone</title>
    <link rel="alternate" href="https://matthewboston.com/blog/stay-out-of-the-dumb-zone.html"/>
    <id>https://matthewboston.com/blog/stay-out-of-the-dumb-zone.html</id>
    <published>2025-12-31T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;The more I work with AI coding tools, the more I realize that context window management is the skill nobody talks about — and the one that matters most.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-dumb-zone"&gt;The Dumb Zone&lt;/h2&gt;

&lt;p&gt;Dex Horthy’s &lt;a href="https://www.youtube.com/watch?v=rmvDxxNubIg"&gt;“No Vibes Allowed”&lt;/a&gt; concept introduced a useful mental model: as your AI tool’s context window fills up, the quality of its output degrades. Not gradually — it falls off a cliff. One minute it’s writing thoughtful, well-structured code. The next it’s hallucinating function signatures and forgetting what file it’s working in.&lt;/p&gt;

&lt;p&gt;That cliff is the dumb zone. And most people don’t notice they’ve entered it until they’re already debugging nonsense.&lt;/p&gt;

&lt;h2 id="compact-often"&gt;Compact Often&lt;/h2&gt;

&lt;p&gt;The fix is simple but requires discipline: compact often. Don’t wait until the context window is full. Don’t wait until the output starts getting weird. Treat compaction as routine maintenance, not emergency triage.&lt;/p&gt;

&lt;p&gt;This means treating research, planning, and implementation as first-class phases of work — not one continuous stream of consciousness. When you finish a research phase, compact. When you finish planning, compact. Start each phase with a clean context that’s focused on the task at hand. I built on this idea in &lt;a href="/blog/a-bad-line-of-research-is-a-hundred-bad-lines-of-code"&gt;A Bad Line of Research Is a Hundred Bad Lines of Code&lt;/a&gt; — the Research-Plan-Implement workflow is how you put this discipline into practice.&lt;/p&gt;

&lt;h2 id="measuring-what-matters"&gt;Measuring What Matters&lt;/h2&gt;

&lt;p&gt;Anthropic recently added context window usage to the &lt;code&gt;/statusline&lt;/code&gt; configuration in Claude Code. It’s a small thing, but it changes the game. You can now see in real time how much of your context window you’ve consumed. Here’s &lt;a href="https://github.com/bostonaholic/dotfiles/blob/main/claude/statusline.clj"&gt;my statusline configuration&lt;/a&gt; if you want to try it yourself.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/claude-code-statusline.jpg" alt="Claude Code statusline showing 24% context usage"&gt;&lt;/p&gt;

&lt;p&gt;Combining this with the dumb zone model gives you a concrete rule: when you hit 40% capacity, it’s time to compact. That’s the threshold where I’ve found the output starts to lose coherence. Before 40%, the model has enough room to reason well. After 40%, you’re gambling.&lt;/p&gt;

&lt;h2 id="a-practice-not-a-feature"&gt;A Practice, Not a Feature&lt;/h2&gt;

&lt;p&gt;Context management isn’t a feature the tool should handle for you. It’s a practice you develop as a user. The best results come from being intentional about what’s in your context window at any given moment — keeping it focused, relevant, and lean.&lt;/p&gt;

&lt;p&gt;The engineers getting the most out of AI tools aren’t the ones with the longest conversations. They’re the ones who know when to start fresh.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>I Got Tired of Writing Bad Commit Messages</title>
    <link rel="alternate" href="https://matthewboston.com/blog/i-got-tired-of-writing-bad-commit-messages.html"/>
    <id>https://matthewboston.com/blog/i-got-tired-of-writing-bad-commit-messages.html</id>
    <published>2025-12-05T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;I built &lt;code&gt;git ai-commit&lt;/code&gt; because I was tired of writing commit messages that said nothing useful — and I knew Claude could do better with the diff right in front of it.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-problem-with-commit-messages"&gt;The Problem with Commit Messages&lt;/h2&gt;

&lt;p&gt;We all know what good commit messages look like. Conventional format, present tense, focused on &lt;em&gt;why&lt;/em&gt; not &lt;em&gt;what&lt;/em&gt;. We’ve all read the blog posts and nodded along. And then we finish a three-hour debugging session and type &lt;code&gt;fix stuff&lt;/code&gt; because our brains are fried.&lt;/p&gt;

&lt;p&gt;The gap between knowing what a good commit message looks like and consistently writing one is discipline — the kind that’s hardest to maintain when you need it most. After a deep focus session, the last thing you want to do is context-switch to writing clear prose about what you just did. It’s the same friction I ran into with performance reviews, which led me to &lt;a href="/blog/i-built-an-ai-tool-to-write-my-brag-document"&gt;build Reflect&lt;/a&gt; — another case where the data was already there, just waiting for an LLM to synthesize it.&lt;/p&gt;

&lt;h2 id="let-the-diff-speak-for-itself"&gt;Let the Diff Speak for Itself&lt;/h2&gt;

&lt;p&gt;The insight behind &lt;code&gt;git ai-commit&lt;/code&gt; is simple: the staged diff already contains everything needed to write a good commit message. The changes are right there — what was added, what was removed, what was modified. An LLM can read that diff and generate a conventional commit message that’s better than what most of us write by hand, especially at the end of a long day.&lt;/p&gt;

&lt;p&gt;It examines your staged changes, understands the intent behind them, and produces a commit message that follows conventional commit format. No more &lt;code&gt;fix stuff&lt;/code&gt;. No more &lt;code&gt;update code&lt;/code&gt;. No more staring at the terminal trying to summarize two hours of work in one line.&lt;/p&gt;

&lt;h2 id="meeting-you-where-you-work"&gt;Meeting You Where You Work&lt;/h2&gt;

&lt;p&gt;The real win came when I integrated it with Emacs and magit-mode. A keybinding — &lt;code&gt;C-c C-a&lt;/code&gt; — and the commit message appears in the buffer, ready to review and edit. No context switch. No separate terminal. It’s just part of the flow.&lt;/p&gt;

&lt;p&gt;That’s the design principle: AI tools should meet you where you already work, not ask you to change your workflow. The best developer tools are invisible. They reduce friction at the exact moment friction appears, and then get out of the way. I expanded on this in &lt;a href="/blog/every-cli-should-include-agent-skills"&gt;Every CLI Should Include Agent Skills&lt;/a&gt; — the tools that adopt context awareness and reasoning first will have a massive advantage.&lt;/p&gt;

&lt;h2 id="not-a-replacement-for-thinking"&gt;Not a Replacement for Thinking&lt;/h2&gt;

&lt;p&gt;This isn’t about removing humans from the commit process. You still review the message. You still edit it if it’s wrong. You still decide what gets staged and what doesn’t. The tool handles the mechanical part — translating a diff into prose — so you can focus on the judgment calls.&lt;/p&gt;

&lt;p&gt;The best commit messages tell a story about &lt;em&gt;why&lt;/em&gt; a change was made. The diff tells you &lt;em&gt;what&lt;/em&gt; changed. Bridging that gap is where &lt;code&gt;git ai-commit&lt;/code&gt; helps, but the &lt;em&gt;why&lt;/em&gt; still comes from you.&lt;/p&gt;

&lt;h2 id="try-it-yourself"&gt;Try It Yourself&lt;/h2&gt;

&lt;p&gt;The scripts are in my &lt;a href="https://github.com/bostonaholic/dotfiles"&gt;dotfiles&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;a href="https://github.com/bostonaholic/dotfiles/blob/main/bin/git-ai-commit-msg"&gt;&lt;code&gt;git-ai-commit-msg&lt;/code&gt;&lt;/a&gt; — the core engine. It reads your staged diff and recent commit history, sends them to Claude, and outputs a conventional commit message to stdout. Non-interactive, no side effects — just a message. This is the script you can wire into hooks, editor integrations, or any automation.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://github.com/bostonaholic/dotfiles/blob/main/bin/git-ai-commit"&gt;&lt;code&gt;git-ai-commit&lt;/code&gt;&lt;/a&gt; — the interactive wrapper. It calls &lt;code&gt;git-ai-commit-msg&lt;/code&gt; under the hood, displays the generated message, and prompts you to accept, edit, or reject it before committing. This is the one you use from the command line as &lt;code&gt;git ai-commit&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop them in your &lt;code&gt;$PATH&lt;/code&gt; and Git picks them up as subcommands automatically.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Your Tests That Make Network Requests Are Flaky</title>
    <link rel="alternate" href="https://matthewboston.com/blog/your-tests-that-make-network-requests-are-flaky.html"/>
    <id>https://matthewboston.com/blog/your-tests-that-make-network-requests-are-flaky.html</id>
    <published>2025-11-20T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Your tests that make network requests are, by definition, flaky tests.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-fallacy"&gt;The Fallacy&lt;/h2&gt;

&lt;p&gt;One of the Fallacies of Distributed Computing is “the network is reliable.” It’s not. Networks drop, slow down, or just decide to misbehave mid-test. Any time your test depends on the network, you’re accepting those failure modes too.&lt;/p&gt;

&lt;p&gt;That’s why network-bound tests flake in CI while passing locally. Your local machine has a fast, stable connection to itself. CI runners talk to external services over real networks with real latency, real packet loss, and real DNS hiccups.&lt;/p&gt;

&lt;h2 id="the-damage-of-flaky-tests"&gt;The Damage of Flaky Tests&lt;/h2&gt;

&lt;p&gt;Flaky tests erode trust in the test suite. When a test fails and the first instinct is “just re-run it,” you’ve already lost. The team stops investigating failures, stops trusting green builds, and eventually stops writing tests for the parts of the system that are hard to test reliably.&lt;/p&gt;

&lt;p&gt;This isn’t hypothetical. I’ve seen teams with 200-test suites where 15 tests are “known flaky.” Those 15 tests train the entire team to ignore test failures. And when a real bug causes a failure, it gets lost in the noise.&lt;/p&gt;

&lt;h2 id="mocks-and-fakes-exist-for-a-reason"&gt;Mocks and Fakes Exist for a Reason&lt;/h2&gt;

&lt;p&gt;The solution is well-established: don’t hit the network in your unit and integration tests. Use &lt;a href="https://martinfowler.com/bliki/TestDouble.html"&gt;mocks, stubs, and fakes&lt;/a&gt; to simulate external dependencies. Record and replay HTTP interactions for tests that need realistic responses.&lt;/p&gt;

&lt;p&gt;But use them sparingly and intentionally. A test that mocks everything tests nothing. The goal is to isolate the specific behavior you’re verifying while keeping the test deterministic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract tests&lt;/strong&gt; verify that your mocks match reality. They run against the actual external service on a schedule (not on every commit) and alert you when the interface changes. This gives you the speed and reliability of mocks with the confidence of real integration.&lt;/p&gt;

&lt;h2 id="the-right-layer-for-network-tests"&gt;The Right Layer for Network Tests&lt;/h2&gt;

&lt;p&gt;Save actual network requests for a small number of smoke tests or end-to-end tests that run in a controlled environment. These tests should be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Clearly marked as network-dependent&lt;/li&gt;
  &lt;li&gt;Run separately from the main test suite&lt;/li&gt;
  &lt;li&gt;Expected to occasionally fail due to external factors&lt;/li&gt;
  &lt;li&gt;Never gating a deploy on their own&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your core test suite — the one that runs on every commit and blocks merges — should be deterministic. No network calls, no database dependencies, no file system assumptions. Fast, reliable, and trustworthy. That’s the foundation everything else builds on — and it’s doubly important when &lt;a href="/blog/coding-agents-need-a-faster-feedback-loop"&gt;coding agents depend on that same feedback loop&lt;/a&gt;. A flaky test wastes human patience, but it wastes agent time and tokens at scale.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Over-Engineering Is Just Future-Proofing Gone Too Far</title>
    <link rel="alternate" href="https://matthewboston.com/blog/over-engineering-is-just-future-proofing-gone-too-far.html"/>
    <id>https://matthewboston.com/blog/over-engineering-is-just-future-proofing-gone-too-far.html</id>
    <published>2025-10-20T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Over-engineering is just future-proofing gone too far.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-intention-is-good"&gt;The Intention Is Good&lt;/h2&gt;

&lt;p&gt;We want systems that can adapt. That’s a reasonable goal. Software requirements change, scale increases, and new features get added. Building systems that can handle change is genuinely important.&lt;/p&gt;

&lt;p&gt;The problem is when “building for change” becomes “building for every possible change.” That’s where future-proofing crosses into over-engineering.&lt;/p&gt;

&lt;h2 id="what-over-engineering-looks-like"&gt;What Over-Engineering Looks Like&lt;/h2&gt;

&lt;p&gt;You’ve seen it. The factory pattern wrapping a class that will only ever have one implementation. The plugin architecture for a feature that will never be extended. The configuration system that supports twelve deployment environments when you only have two.&lt;/p&gt;

&lt;p&gt;Each of these decisions was probably justified at the time with “we might need this later.” But later rarely comes in the form you predicted. More often, you end up maintaining complexity that serves no current purpose while the actual changes you need to make require reworking the over-engineered parts anyway.&lt;/p&gt;

&lt;h2 id="yagni-keeps-us-honest"&gt;YAGNI Keeps Us Honest&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/bliki/Yagni.html"&gt;You Aren’t Gonna Need It.&lt;/a&gt; The principle is simple: don’t build something until you actually need it. Build what you need now, in a way that makes future changes possible.&lt;/p&gt;

&lt;p&gt;That second part is important. YAGNI doesn’t mean writing throwaway code. It means writing clean, well-structured code that solves today’s problem without also solving tomorrow’s hypothetical problems. Good code is naturally extensible. You don’t need to add extension points for extensions that don’t exist yet.&lt;/p&gt;

&lt;h2 id="the-art-of-balance"&gt;The Art of Balance&lt;/h2&gt;

&lt;p&gt;The art is balancing flexibility without designing for problems that may never come. Here’s how I think about it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;One concrete use case:&lt;/strong&gt; Just write the code. No abstraction needed.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Two concrete use cases:&lt;/strong&gt; Look for common patterns, but don’t force an abstraction.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Three concrete use cases:&lt;/strong&gt; Now you have enough information to design a good abstraction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Rule of Three isn’t arbitrary. It’s about having enough data points to see the real pattern instead of the imagined one.&lt;/p&gt;

&lt;h2 id="the-cost-of-getting-it-wrong"&gt;The Cost of Getting It Wrong&lt;/h2&gt;

&lt;p&gt;Over-engineered code isn’t just wasted effort at creation time. It’s ongoing cost. Every unnecessary abstraction is another thing to understand, test, and maintain. Every unused configuration option is another thing that can break. Every premature generalization makes the specific thing you actually need harder to find and change. This is the difference between &lt;a href="/blog/complex-is-better-than-complicated"&gt;complexity and complication&lt;/a&gt; – one serves a real need, the other just adds weight.&lt;/p&gt;

&lt;p&gt;Keep it simple. Build what you need. Make it clean enough that change is easy when — not if — the requirements evolve. That’s not under-engineering. That’s engineering.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Building a Platform Is Raising the Floor for Everyone</title>
    <link rel="alternate" href="https://matthewboston.com/blog/building-a-platform-is-raising-the-floor-for-everyone.html"/>
    <id>https://matthewboston.com/blog/building-a-platform-is-raising-the-floor-for-everyone.html</id>
    <published>2025-06-13T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;A platform is a raised level surface on which people or things can stand. That definition is the whole philosophy of platform engineering in one sentence.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="raising-the-floor-not-just-the-ceiling"&gt;Raising the Floor, Not Just the Ceiling&lt;/h2&gt;

&lt;p&gt;Most engineering conversations focus on the ceiling — how fast can the best team ship, how elegant can the architecture get, how sophisticated can the tooling be. Platform engineering focuses on something more valuable: how good is the &lt;em&gt;worst&lt;/em&gt; outcome?&lt;/p&gt;

&lt;p&gt;A good platform raises the floor. It takes the hard stuff and makes it easier. It takes the risky stuff and makes it safer. The team that’s never configured a deployment pipeline shouldn’t have to learn the hard way. The team that’s never thought about secrets management shouldn’t be able to accidentally commit credentials.&lt;/p&gt;

&lt;h2 id="shared-tools-clear-patterns-built-in-guardrails"&gt;Shared Tools, Clear Patterns, Built-In Guardrails&lt;/h2&gt;

&lt;p&gt;The three ingredients of a good platform are simple:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Shared tools&lt;/strong&gt; so teams aren’t reinventing the same solutions in isolation. One CI pipeline, one deployment mechanism, one logging stack. Not because uniformity is a goal in itself, but because every bespoke solution is a maintenance burden and a knowledge silo.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Clear patterns&lt;/strong&gt; so the right way to do something is also the easy way. If your platform makes the secure path harder than the insecure path, people will choose the insecure path. Every time. Good patterns encode good decisions so teams don’t have to make them from scratch.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Built-in guardrails&lt;/strong&gt; so mistakes are caught before they cause damage. Not after deployment, not after the incident, not after the postmortem. Guardrails shift the cost of errors left — catching them when they’re cheap to fix instead of expensive to recover from.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="everyone-benefits-not-just-the-experts"&gt;Everyone Benefits, Not Just the Experts&lt;/h2&gt;

&lt;p&gt;This is the part that makes platform engineering worth the investment. Without a platform, quality and security depend on the expertise of individual teams. Some teams are great at it. Some aren’t. The result is uneven — pockets of excellence surrounded by pockets of risk.&lt;/p&gt;

&lt;p&gt;A platform levels that playing field. The team that doesn’t have a security expert still gets secure defaults. This matters more than ever now that &lt;a href="/blog/vibe-coded-apps-need-real-security"&gt;anyone can vibe-code an app in an afternoon&lt;/a&gt; — without a platform providing guardrails, security knowledge becomes a prerequisite that most builders don’t have. The team that’s never operated a production service still gets observability and alerting. The worst-case outcomes get better, and the best work becomes more repeatable.&lt;/p&gt;

&lt;h2 id="the-best-work-becomes-repeatable"&gt;The Best Work Becomes Repeatable&lt;/h2&gt;

&lt;p&gt;That last point is the one people miss. Platform engineering isn’t just about protecting teams from failure — it’s about making success reproducible. When one team figures out a good pattern, the platform captures it and makes it available to everyone. Knowledge compounds instead of staying locked in one team’s heads.&lt;/p&gt;

&lt;p&gt;The goal of platform engineering isn’t to build a platform. It’s to raise the surface on which everyone else stands.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Ship as Fast as Possible, but Not Faster</title>
    <link rel="alternate" href="https://matthewboston.com/blog/ship-as-fast-as-possible-but-not-faster.html"/>
    <id>https://matthewboston.com/blog/ship-as-fast-as-possible-but-not-faster.html</id>
    <published>2025-06-06T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Mikhail Parakhin said something recently that stuck with me: “Ship as fast as possible, but not faster.” It’s a paraphrase of Einstein’s famous line about simplicity, and it captures the same essential tension.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-pressure-to-ship"&gt;The Pressure to Ship&lt;/h2&gt;

&lt;p&gt;Every engineering organization feels the pull toward speed. Shorter cycles, faster deploys, quicker feedback loops. And that pull is mostly healthy — speed forces prioritization, exposes assumptions early, and gets real user feedback before you’ve over-invested in the wrong direction.&lt;/p&gt;

&lt;p&gt;But there’s a threshold where speed stops being an advantage and starts being a liability. Cross it, and you’re not shipping faster — you’re just creating incidents faster.&lt;/p&gt;

&lt;h2 id="what-too-fast-looks-like"&gt;What “Too Fast” Looks Like&lt;/h2&gt;

&lt;p&gt;You know you’ve crossed the line when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Features ship without meaningful code review because “we need to hit the deadline”&lt;/li&gt;
  &lt;li&gt;Tests get skipped or stubbed out because “we’ll come back to it”&lt;/li&gt;
  &lt;li&gt;On-call pages spike after every deploy cycle&lt;/li&gt;
  &lt;li&gt;The team spends more time fighting fires than building features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these happen because anyone decided quality doesn’t matter. They happen because speed became the goal instead of the tool. When you optimize for shipping speed above all else, quality is the first thing you trade away — and incidents are the invoice that arrives later.&lt;/p&gt;

&lt;h2 id="the-sweet-spot"&gt;The Sweet Spot&lt;/h2&gt;

&lt;p&gt;The sweet spot isn’t slow. It’s disciplined. It’s moving quickly while still doing the things that prevent you from moving backwards: writing tests, reviewing code, validating assumptions before they hit production.&lt;/p&gt;

&lt;p&gt;This isn’t about adding process for the sake of process. It’s about recognizing that the fastest path to delivering value isn’t always the shortest path to deploying code. A feature that ships on Tuesday and works is faster than a feature that ships on Monday and breaks, because the broken feature costs you Tuesday &lt;em&gt;and&lt;/em&gt; Wednesday to fix.&lt;/p&gt;

&lt;h2 id="speed-is-sustainable-only-with-quality"&gt;Speed Is Sustainable Only with Quality&lt;/h2&gt;

&lt;p&gt;The teams that ship the fastest over months and years — not just this sprint — are the ones that invest in quality as a speed multiplier. Good test coverage means you refactor with confidence. Clean code means new engineers ramp up faster. Reliable deploys mean you can ship daily without white-knuckling it.&lt;/p&gt;

&lt;p&gt;Quality isn’t the opposite of speed. It’s the foundation that makes sustained speed possible.&lt;/p&gt;

&lt;p&gt;Ship as fast as possible. But not faster.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Sparse Is Better than Dense</title>
    <link rel="alternate" href="https://matthewboston.com/blog/sparse-is-better-than-dense.html"/>
    <id>https://matthewboston.com/blog/sparse-is-better-than-dense.html</id>
    <published>2025-05-20T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;PEP 20 got it right. “Sparse is better than dense.” Code packed too tightly is hard to read and easy to misinterpret. Whitespace isn’t wasted space – it’s how you communicate intent.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="density-is-a-readability-tax"&gt;Density Is a Readability Tax&lt;/h2&gt;

&lt;p&gt;Dense code feels efficient. Everything on one line, no blank lines between blocks, maximum logic per screen. It looks like you accomplished a lot. But reading it requires holding the entire context in your head simultaneously, and that’s where bugs hide.&lt;/p&gt;

&lt;p&gt;When a function does six things in twelve lines, the reader has to mentally decompose it before they can reason about any single part. That decomposition is work. Every person who reads that code pays that tax. The author pays it once; the readers pay it forever.&lt;/p&gt;

&lt;h2 id="what-sparse-actually-means"&gt;What Sparse Actually Means&lt;/h2&gt;

&lt;p&gt;Sparse doesn’t mean verbose. It doesn’t mean adding blank lines randomly or padding everything with comments. It means giving each idea room to breathe:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;One idea per line.&lt;/strong&gt; If a line does two things, split it.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Short functions with clear names.&lt;/strong&gt; Let the function name carry the “what” so the body only needs to carry the “how.”&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Blank lines as paragraph breaks.&lt;/strong&gt; Group related statements. Separate unrelated ones. The visual structure should mirror the logical structure.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Avoid deeply nested logic.&lt;/strong&gt; Early returns, guard clauses, and extracting helper functions all reduce nesting and make the happy path obvious.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is code where intent jumps out at you instead of hiding behind clever one-liners.&lt;/p&gt;

&lt;h2 id="the-compression-trap"&gt;The Compression Trap&lt;/h2&gt;

&lt;p&gt;There’s a strong pull toward compression, especially among experienced developers. You learn a language well enough to chain three operations into one expression, and it feels like mastery. Sometimes it is. But more often it’s trading your time now for everyone else’s time later.&lt;/p&gt;

&lt;p&gt;This is a close cousin of &lt;a href="/blog/over-engineering-is-just-future-proofing-gone-too-far"&gt;over-engineering&lt;/a&gt; – both are cases where the author optimizes for the wrong thing. Over-engineering optimizes for hypothetical futures. Dense code optimizes for brevity at the cost of clarity. Neither serves the people who maintain the code.&lt;/p&gt;

&lt;h2 id="sparse-code-scales-better"&gt;Sparse Code Scales Better&lt;/h2&gt;

&lt;p&gt;In a codebase touched by many people – or many agents – sparse code has a compounding advantage. It’s easier to review, easier to diff, and easier to merge. When each line does one thing, a changed line in a pull request tells you exactly what changed and why. Dense code hides changes inside complex expressions where a reviewer has to parse the whole thing to find the difference.&lt;/p&gt;

&lt;p&gt;This matters even more now that &lt;a href="/blog/code-quality-still-matters"&gt;code quality&lt;/a&gt; has to survive both human and AI authorship. AI-generated code tends toward density because models optimize for correctness, not readability. Sparse code is the antidote: it forces structure that makes intent visible regardless of who – or what – wrote it.&lt;/p&gt;

&lt;h2 id="the-zen-got-it-right"&gt;The Zen Got It Right&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://peps.python.org/pep-0020/"&gt;The Zen of Python&lt;/a&gt; isn’t really about Python. It’s about software. “Sparse is better than dense” is a design principle that applies to any language, any codebase, any team. The same goes for &lt;a href="/blog/complex-is-better-than-complicated"&gt;the line between complex and complicated&lt;/a&gt; – it’s a universal design principle hiding in a Python style guide. Give your code room to breathe. Your future readers will thank you.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7330595465068969985/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Flat Is Better Than Nested</title>
    <link rel="alternate" href="https://matthewboston.com/blog/flat-is-better-than-nested.html"/>
    <id>https://matthewboston.com/blog/flat-is-better-than-nested.html</id>
    <published>2025-04-04T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Deeply nested code is a readability tax. Every level of indentation is another thing the reader has to hold in their head.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-zen-got-it-right"&gt;The Zen Got It Right&lt;/h2&gt;

&lt;p&gt;“Flat is better than nested” is one of the principles from the &lt;a href="https://peps.python.org/pep-0020/"&gt;Zen of Python&lt;/a&gt;, but it applies far beyond Python. It’s a statement about how humans process structure. We read code linearly. When code nests deeply, we have to maintain a mental stack of context – which condition brought us here, which loop we’re inside, which branch we took three levels up. That stack has a cost, and it’s paid by every person who reads the code after it’s written.&lt;/p&gt;

&lt;p&gt;Flat code respects the reader. It says what it means at one level of abstraction and moves on.&lt;/p&gt;

&lt;h2 id="what-deep-nesting-actually-costs"&gt;What Deep Nesting Actually Costs&lt;/h2&gt;

&lt;p&gt;The problem with nesting isn’t aesthetic. It’s cognitive. Consider what happens when you encounter a function with four levels of indentation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You lose sight of the function’s main purpose.&lt;/li&gt;
  &lt;li&gt;Error handling and edge cases blur together with the happy path.&lt;/li&gt;
  &lt;li&gt;Each condition depends on every condition above it, creating &lt;a href="/blog/explicit-is-better-than-implicit"&gt;implicit coupling&lt;/a&gt; the reader has to reconstruct.&lt;/li&gt;
  &lt;li&gt;Testing becomes harder because you need to set up the full context stack to reach inner branches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nested code hides intent behind structure. Flat code puts intent front and center.&lt;/p&gt;

&lt;h2 id="strategies-for-flattening"&gt;Strategies for Flattening&lt;/h2&gt;

&lt;p&gt;Flattening isn’t about cramming everything onto one level. It’s about choosing structures that reduce the reader’s cognitive burden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guard clauses&lt;/strong&gt; are the simplest tool. Instead of wrapping an entire function body in an &lt;code&gt;if&lt;/code&gt; block, check for the failure case early and return. Each guard clause eliminates a level of nesting and makes the preconditions explicit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extract and name.&lt;/strong&gt; When a nested block does something coherent, pull it into a named function. The name replaces the structure as the carrier of meaning. You don’t need to trace through the nesting to understand what’s happening – the name tells you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipelines over loops.&lt;/strong&gt; Chaining &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and &lt;code&gt;reduce&lt;/code&gt; (or their equivalents) keeps data transformations flat and composable. Each step does one thing. Compare that to a nested loop with conditionals – same result, far more mental overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data structures over control flow.&lt;/strong&gt; Sometimes deep nesting is a sign that logic belongs in a lookup table or a configuration map rather than a chain of &lt;code&gt;if/else&lt;/code&gt; branches. Let the data carry the decisions.&lt;/p&gt;

&lt;h2 id="flat-applies-beyond-code"&gt;Flat Applies Beyond Code&lt;/h2&gt;

&lt;p&gt;This principle scales. Flat organizational structures reduce communication overhead. Flat directory layouts make projects navigable. Flat data schemas are easier to query and extend. The insight is the same everywhere: unnecessary hierarchy adds friction.&lt;/p&gt;

&lt;p&gt;The same instinct that leads to &lt;a href="/blog/over-engineering-is-just-future-proofing-gone-too-far"&gt;over-engineering&lt;/a&gt; often leads to over-nesting. We add layers because we &lt;em&gt;might&lt;/em&gt; need them, not because we do. YAGNI applies to structure just as much as it applies to features.&lt;/p&gt;

&lt;h2 id="simplicity-takes-discipline"&gt;Simplicity Takes Discipline&lt;/h2&gt;

&lt;p&gt;Writing flat code is harder than writing nested code. Nested code is what falls out of your fingers when you’re thinking through a problem sequentially – “if this, then if that, then if the other thing.” Flattening requires you to step back, see the structure, and reshape it for the reader.&lt;/p&gt;

&lt;p&gt;That discipline is the same one behind every other quality practice. It’s easier to skip tests, easier to skip reviews, easier to let complexity accumulate. But the cost compounds. A little nesting here, a few extra branches there, and six months later you’re staring at a function nobody wants to touch.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Keep it flat. Your future self – and everyone else who reads your code – will thank you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7313897296776110080/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>I Built an AI Tool to Write My Brag Document</title>
    <link rel="alternate" href="https://matthewboston.com/blog/i-built-an-ai-tool-to-write-my-brag-document.html"/>
    <id>https://matthewboston.com/blog/i-built-an-ai-tool-to-write-my-brag-document.html</id>
    <published>2025-03-29T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;I always found it difficult to summarize my impact during review cycles. So I built &lt;a href="https://github.com/bostonaholic/reflect"&gt;Reflect&lt;/a&gt; to do it for me.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-review-cycle-problem"&gt;The Review Cycle Problem&lt;/h2&gt;

&lt;p&gt;Every six months, the same ritual. You sit down, open a blank document, and try to reconstruct what you accomplished. You scan through PRs, skim commit histories, and squint at Jira tickets trying to jog your memory. Half of it is gone. The stuff you remember is whatever happened in the last few weeks, not the critical fix you shipped four months ago.&lt;/p&gt;

&lt;p&gt;Some people keep running brag documents. They write down wins as they happen. That’s great advice, and I’ve tried it. The problem is discipline. When you’re deep in a sprint, the last thing on your mind is updating a side document about how impactful your current work is. So the document stays empty until review season, and you’re back to square one.&lt;/p&gt;

&lt;h2 id="let-your-git-history-tell-the-story"&gt;Let Your Git History Tell the Story&lt;/h2&gt;

&lt;p&gt;The data already exists. Every PR you merged, every issue you closed, every code review you did – it’s all sitting in GitHub. The problem isn’t missing information. It’s that the information is scattered across dozens of repositories and hundreds of individual items with no narrative thread connecting them.&lt;/p&gt;

&lt;p&gt;Reflect pulls that data together. It fetches your merged pull requests and closed issues, filters by date range, and feeds it all to an LLM that generates a coherent summary of your contributions. The output is a set of markdown documents – a detailed contribution log, a technical summary, and a professional achievement document ready for review season.&lt;/p&gt;

&lt;p&gt;You point it at your GitHub activity, tell it how far back to look, and it does the rest. No manual curation. No forgetting that critical infrastructure migration you led in Q2.&lt;/p&gt;

&lt;h2 id="what-the-data-doesnt-capture"&gt;What the Data Doesn’t Capture&lt;/h2&gt;

&lt;p&gt;Here’s the thing I feel strongly about: your impact extends far beyond what shows up in GitHub. Mentoring a junior engineer through their first production incident. Pushing back on a bad architectural decision in a meeting. Writing the design doc that aligned three teams. Facilitating a retro that actually changed how the team works. None of that generates a pull request.&lt;/p&gt;

&lt;p&gt;Tanya Reilly calls this &lt;a href="https://noidea.dog/glue"&gt;glue work&lt;/a&gt; – the work that holds teams together but rarely shows up in metrics. Reflect can summarize your code contributions, but the full picture of your impact requires your own voice. Use the generated document as a starting point, not the final draft.&lt;/p&gt;

&lt;h2 id="build-the-tool-you-wish-you-had"&gt;Build the Tool You Wish You Had&lt;/h2&gt;

&lt;p&gt;The best developer tools come from scratching your own itch. You hit a friction point, you realize the data to solve it already exists, and you write something to bridge the gap. That’s exactly how &lt;a href="/blog/i-got-tired-of-writing-bad-commit-messages"&gt;git ai-commit&lt;/a&gt; happened – the staged diff already had everything needed for a good commit message, so I let an LLM write it.&lt;/p&gt;

&lt;p&gt;Reflect follows the same pattern. The raw material for your brag document is already in GitHub’s API. The LLM just needs to read it and synthesize a narrative. The hardest part wasn’t the AI – it was deciding what filters, date ranges, and output formats would actually be useful during review prep.&lt;/p&gt;

&lt;p&gt;If you hit a recurring pain point in your workflow, build the tool. Open source it. Someone else has the same problem.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Your GitHub history is a goldmine that most people only dig through once or twice a year. Stop doing it by hand.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7311885477546991616/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Complex Is Better Than Complicated</title>
    <link rel="alternate" href="https://matthewboston.com/blog/complex-is-better-than-complicated.html"/>
    <id>https://matthewboston.com/blog/complex-is-better-than-complicated.html</id>
    <published>2025-03-28T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Complexity is the cost of solving a real problem. Complication is the cost of solving it badly. PEP 20 draws a clear line between the two: “Complex is better than complicated.”&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-distinction-matters"&gt;The Distinction Matters&lt;/h2&gt;

&lt;p&gt;These two words get used interchangeably, but they mean different things.&lt;/p&gt;

&lt;p&gt;Complexity is inherent. Some problems are genuinely hard. A distributed consensus algorithm is complex. A tax calculation with fifty jurisdictions is complex. You can’t make these simpler without losing correctness.&lt;/p&gt;

&lt;p&gt;Complication is incidental. It’s the complexity you add on top of the problem – the unnecessary layers, the premature abstractions, the clever indirection that serves no current purpose. Complication doesn’t come from the problem. It comes from the solution.&lt;/p&gt;

&lt;h2 id="when-complexity-is-warranted"&gt;When Complexity Is Warranted&lt;/h2&gt;

&lt;p&gt;Not all complexity is bad. A circuit breaker in a distributed system adds complexity, but it prevents cascading failures. A caching layer adds complexity, but it keeps your application responsive under load. These are deliberate trade-offs: you accept the cognitive cost because the benefit is real and immediate.&lt;/p&gt;

&lt;p&gt;The key word is &lt;em&gt;warranted&lt;/em&gt;. Warranted complexity solves a problem you actually have. You can point to the requirement, the failure mode, or the constraint that demands it. If you can’t articulate why the complexity exists, it’s probably complication in disguise.&lt;/p&gt;

&lt;h2 id="how-complication-sneaks-in"&gt;How Complication Sneaks In&lt;/h2&gt;

&lt;p&gt;Complication rarely arrives as an obvious mistake. It accumulates through reasonable-sounding decisions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Let’s add an abstraction layer so we can swap implementations later.” (You never swap.)&lt;/li&gt;
  &lt;li&gt;“Let’s make this configurable so it works for every team.” (Only one team uses it.)&lt;/li&gt;
  &lt;li&gt;“Let’s build a plugin system in case we need extensibility.” (You don’t.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each decision makes sense in isolation. Together, they bury the actual logic under layers that exist to serve hypothetical futures. This is the same instinct that drives &lt;a href="/blog/over-engineering-is-just-future-proofing-gone-too-far"&gt;over-engineering&lt;/a&gt; – building for problems you don’t have yet.&lt;/p&gt;

&lt;h2 id="choosing-complexity-over-complication"&gt;Choosing Complexity Over Complication&lt;/h2&gt;

&lt;p&gt;The discipline is knowing when to accept complexity and when to refuse complication. A few heuristics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Can you explain the complexity in one sentence?&lt;/strong&gt; If the justification is clear and concrete, it’s probably warranted. If it takes a paragraph of hypotheticals, it’s probably complication.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Does removing it break something today?&lt;/strong&gt; Warranted complexity is load-bearing. Complication can be removed without consequence.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Did you add it to solve a problem, or to prevent one?&lt;/strong&gt; Solving real problems creates complexity. Preventing imaginary problems creates complication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn’t to minimize complexity at all costs. It’s to ensure that every piece of complexity earns its place.&lt;/p&gt;

&lt;h2 id="the-zen-got-it-right"&gt;The Zen Got It Right&lt;/h2&gt;

&lt;p&gt;“Complex is better than complicated” is not a license to build complex systems. It’s a reminder that when a problem demands complexity, the answer is to embrace that complexity honestly – not to paper over it with layers of complication that make the system harder to understand.&lt;/p&gt;

&lt;p&gt;Simple when you can. Complex when you must. Complicated never.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7311373789701779457/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Simple Is Better Than Complex</title>
    <link rel="alternate" href="https://matthewboston.com/blog/simple-is-better-than-complex.html"/>
    <id>https://matthewboston.com/blog/simple-is-better-than-complex.html</id>
    <published>2025-03-27T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;PEP 20’s most quoted line is also its most ignored. Everyone agrees that simple is better than complex. Almost nobody writes simple code on the first try.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="complexity-is-the-default"&gt;Complexity Is the Default&lt;/h2&gt;

&lt;p&gt;Complex code is easy to write. You think through a problem sequentially, handle each case as it appears, and the result is a tangle of branches and edge cases that technically works. No discipline required.&lt;/p&gt;

&lt;p&gt;Simple code is the opposite. It requires you to understand the problem deeply enough to find the clean solution – the one that handles the cases without special-casing them, that expresses the logic without obscuring it. That understanding takes time. Simplicity is the output of effort, not the absence of it.&lt;/p&gt;

&lt;p&gt;This is why &lt;a href="/blog/over-engineering-is-just-future-proofing-gone-too-far"&gt;over-engineering&lt;/a&gt; is so common. It feels like progress. Adding layers, abstractions, and configuration options feels like building something robust. But robustness comes from simplicity, not from complexity with guardrails.&lt;/p&gt;

&lt;h2 id="simple-doesnt-mean-easy"&gt;Simple Doesn’t Mean Easy&lt;/h2&gt;

&lt;p&gt;There’s a critical distinction between simple and easy. Easy is what you reach for first. Simple is what you arrive at after removing everything that doesn’t need to be there.&lt;/p&gt;

&lt;p&gt;A function with one responsibility is simple. A class that does one thing well is simple. A system where you can trace a request from input to output without jumping through six layers of indirection is simple. None of these are easy to design. All of them are easy to maintain.&lt;/p&gt;

&lt;p&gt;The Zen of Python follows “simple is better than complex” with “complex is better than complicated.” That ordering matters. Sometimes genuine complexity exists in the problem domain. The principle isn’t to deny that complexity – it’s to never add accidental complexity on top of it.&lt;/p&gt;

&lt;h2 id="the-readability-dividend"&gt;The Readability Dividend&lt;/h2&gt;

&lt;p&gt;Simple code pays compound interest in readability. When a function is short, named well, and does one thing, you can read it in seconds. When a module has a clear interface and minimal dependencies, you can reason about it in isolation. When a system is composed of simple parts, you can understand the whole by understanding the pieces.&lt;/p&gt;

&lt;p&gt;Every layer of unnecessary complexity breaks this. A reader hits an abstraction and has to ask: “Why is this here? What problem does this solve?” If the answer is “none, currently,” you’ve just spent their attention on nothing.&lt;/p&gt;

&lt;h2 id="simplicity-is-a-practice"&gt;Simplicity Is a Practice&lt;/h2&gt;

&lt;p&gt;Writing simple code isn’t a one-time decision. It’s a practice you apply at every level, every day:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Before adding a new abstraction, ask whether the concrete code is actually hard to understand.&lt;/li&gt;
  &lt;li&gt;Before adding a configuration option, ask whether anyone will ever change it.&lt;/li&gt;
  &lt;li&gt;Before splitting a function, ask whether the pieces are genuinely independent ideas.&lt;/li&gt;
  &lt;li&gt;Before reaching for a pattern, ask whether the pattern serves the code or the other way around.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer is often “no.” And “no” is the most powerful tool for keeping things simple.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;The Zen got it right. Simple is better than complex. Not because simplicity is easy, but because it’s worth the effort.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7311022022430453761/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Explicit Is Better Than Implicit</title>
    <link rel="alternate" href="https://matthewboston.com/blog/explicit-is-better-than-implicit.html"/>
    <id>https://matthewboston.com/blog/explicit-is-better-than-implicit.html</id>
    <published>2025-03-26T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Implicit code hides decisions. Explicit code puts them where they belong – right in front of you.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-zen-got-it-right"&gt;The Zen Got It Right&lt;/h2&gt;

&lt;p&gt;“Explicit is better than implicit” is one of the core principles from the &lt;a href="https://peps.python.org/pep-0020/"&gt;Zen of Python&lt;/a&gt;, but like &lt;a href="/blog/flat-is-better-than-nested"&gt;flat over nested&lt;/a&gt; and &lt;a href="/blog/sparse-is-better-than-dense"&gt;sparse over dense&lt;/a&gt;, it applies to all software. It’s a statement about communication. Code that states its intent directly is code that can be understood, maintained, and trusted.&lt;/p&gt;

&lt;p&gt;Implicit code does the opposite. It asks the reader to know things that aren’t written down – conventions, defaults, inherited behavior, environmental assumptions. Every implicit decision is a gap the reader has to fill with their own knowledge. Some readers won’t have that knowledge. Some will guess wrong.&lt;/p&gt;

&lt;h2 id="the-cost-of-going-implicit"&gt;The Cost of Going Implicit&lt;/h2&gt;

&lt;p&gt;Implicit code feels convenient at first. You save a few keystrokes. You lean on framework magic. You trust that the next reader will just &lt;em&gt;know&lt;/em&gt; how things work. Then someone new joins the team, and they don’t.&lt;/p&gt;

&lt;p&gt;The costs show up in predictable ways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Debugging takes longer.&lt;/strong&gt; When behavior is implicit, you can’t just read the code to understand what’s happening. You have to trace through framework internals, check configuration files, and reconstruct the hidden control flow.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Onboarding slows down.&lt;/strong&gt; New team members have to learn the invisible rules before they can be productive. The more implicit knowledge a codebase requires, the steeper the learning curve.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Bugs hide in assumptions.&lt;/strong&gt; Implicit defaults change between versions. Implicit type coercions produce surprising results. Implicit ordering dependencies break when someone adds a step in the wrong place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every implicit decision is a bet that the reader will have the same context you have right now. That bet loses more often than you’d think.&lt;/p&gt;

&lt;h2 id="where-explicitness-pays-off"&gt;Where Explicitness Pays Off&lt;/h2&gt;

&lt;p&gt;Explicitness matters most at the boundaries – the places where different parts of a system communicate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming.&lt;/strong&gt; A function called &lt;code&gt;process&lt;/code&gt; tells you nothing. A function called &lt;code&gt;validate_and_normalize_email&lt;/code&gt; tells you exactly what it does. Explicit names eliminate guesswork.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration.&lt;/strong&gt; Explicit configuration files beat convention-over-configuration when the conventions aren’t well-known. If your team has to look up what the default is, it should be stated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error handling.&lt;/strong&gt; Explicit error handling – checking return values, catching specific exceptions, validating inputs at boundaries – prevents the silent failures that implicit error handling allows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependencies.&lt;/strong&gt; Explicit imports and dependency declarations make it clear what a module needs. Implicit dependency injection or service locators hide relationships that matter for understanding and testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Types.&lt;/strong&gt; Type annotations make contracts visible. When a function signature says it takes a &lt;code&gt;string&lt;/code&gt; and returns an &lt;code&gt;int&lt;/code&gt;, the reader doesn’t have to infer the contract from usage patterns. The code documents itself.&lt;/p&gt;

&lt;h2 id="explicit-doesnt-mean-verbose"&gt;Explicit Doesn’t Mean Verbose&lt;/h2&gt;

&lt;p&gt;There’s a common objection: “Explicit code is too verbose.” But explicitness and verbosity are different things. Verbose code adds words without adding information. Explicit code adds exactly the information that would otherwise be hidden.&lt;/p&gt;

&lt;p&gt;A ten-line function with clear names and explicit error handling isn’t verbose. A one-liner that relies on three implicit type conversions and a default parameter buried in a framework isn’t concise – it’s obscure.&lt;/p&gt;

&lt;p&gt;The goal isn’t to write more code. It’s to write code where intent is visible without external context.&lt;/p&gt;

&lt;h2 id="explicitness-scales"&gt;Explicitness Scales&lt;/h2&gt;

&lt;p&gt;In a small codebase maintained by one person, implicit conventions work fine. You wrote the conventions. You remember them. But codebases grow. Teams change. The conventions that felt obvious to three people become tribal knowledge that twenty people struggle to reconstruct.&lt;/p&gt;

&lt;p&gt;Explicit code doesn’t require shared tribal knowledge to understand. It carries its own context. That’s what makes it resilient to team changes, context switches, and the long gaps between when code is written and when it’s read.&lt;/p&gt;

&lt;p&gt;&lt;a href="/blog/code-quality-still-matters"&gt;Code quality&lt;/a&gt; starts with making intent visible. Explicit code is the most direct way to get there.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Make the implicit explicit. Your code should say what it means without making the reader guess.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7310644570181013506/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Today I Learned: Using BETWEEN with SQL Dates</title>
    <link rel="alternate" href="https://matthewboston.com/blog/today-i-learned-using-between-with-sql-dates.html"/>
    <id>https://matthewboston.com/blog/today-i-learned-using-between-with-sql-dates.html</id>
    <published>2018-08-10T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;I came across some weird behavior today when running PostgreSQL queries with dates. Specifically when using &lt;code&gt;BETWEEN&lt;/code&gt; to search for records that are within some date range. It turns out, the &lt;code&gt;BETWEEN&lt;/code&gt; comparison predicate behaves differently for the &lt;code&gt;date&lt;/code&gt; type as it does for the &lt;code&gt;timestamp&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;Consider the following for table &lt;code&gt;articles&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2018-08-01'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2018-08-07'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For articles, we don’t care about the time the article was posted, only the day.&lt;/p&gt;

&lt;p&gt;In the PostgreSQL documentation for &lt;a href="https://www.postgresql.org/docs/9.6/static/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE"&gt;BETWEEN&lt;/a&gt;, it states that it is &lt;em&gt;inclusive&lt;/em&gt; on both ends e.g. &lt;code&gt;x &amp;lt;= a &amp;lt;= y&lt;/code&gt;. This would mean that our query above would return all articles created between August 1, 2018 and including August 7, 2018. And it does.&lt;/p&gt;

&lt;p&gt;Now, consider the table &lt;code&gt;comments&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2018-08-01'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2018-08-07'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Very similar to &lt;code&gt;articles&lt;/code&gt; except here we do care about the time a comment was posted, so the column type is &lt;code&gt;timestamp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similarly, we want to retrieve all comments that were posted between August 1, 2018 and including August 7, 2018. If you expected the query above to return comments that were created on August 1, 2018 and all comments created on August 7, 2018, you would be wrong.&lt;/p&gt;

&lt;p&gt;I learned from a colleague, “Your target column will be inclusive of that type”. This sounds a bit confusing, so let me explain.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;BETWEEN&lt;/code&gt; creates a &lt;em&gt;time interval&lt;/em&gt;. This means that &lt;code&gt;'2018-08-07'::DATE&lt;/code&gt; is read in as &lt;code&gt;2018-08-07 00:00:00&lt;/code&gt;. Therefor the query is checking if the row will be included within the &lt;em&gt;date&lt;/em&gt; type of &lt;code&gt;2018-08-07 00:00:00&lt;/code&gt;. And it is. &lt;code&gt;'2018-08-07 00:00:00'::DATE&lt;/code&gt; is equal to &lt;code&gt;'2018-08-07'::DATE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, in the query for &lt;code&gt;comments&lt;/code&gt;, the &lt;code&gt;'2018-08-07'::TIMESTAMP&lt;/code&gt; is also read in as &lt;code&gt;2018-08-07 00:00:00&lt;/code&gt;. This means that all comments created during the day of August 7, 2018 will not be included in the results. For instance, a comment created &lt;code&gt;2018-08-07 04:13:47&lt;/code&gt; is not before or equal to &lt;code&gt;2018-08-07 00:00:00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here it is in a more digestible format.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|           |          2018-08-07 | 2018-08-07 04:13:47 | &amp;lt;= 2018-08-07::DATE | &amp;lt;= 2018-08-07::TIMESTAMP |
|-----------|---------------------|---------------------|---------------------|--------------------------|
| date      |          2018-08-07 | 2018-08-07          | true                | true                     |
| timestamp | 2018-08-07 00:00:00 | 2018-08-07 04:13:47 | true                | false                    |

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Today I Learned: Docker Compose Networks</title>
    <link rel="alternate" href="https://matthewboston.com/blog/today-i-learned-docker-compose-networks.html"/>
    <id>https://matthewboston.com/blog/today-i-learned-docker-compose-networks.html</id>
    <published>2018-06-14T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Due to a bug fix or maybe a new feature to Docker Compose, I caught myself yak shaving how Docker and Docker Compose handle local networking.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;

&lt;p&gt;I have 2 services, let’s call them &lt;code&gt;foo-service&lt;/code&gt; and &lt;code&gt;bar-service&lt;/code&gt;. They both live in their respective directories as siblings.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── bar-service
│   ├── docker-compose.yml
│   └── src
└── foo-service
    ├── docker-compose.yml
    └── src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;bar-service&lt;/code&gt; refers to &lt;code&gt;foo-service&lt;/code&gt;’s network like so:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bar-service/docker-compose.yml&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fooservice_default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="the-yak-shave"&gt;The Yak Shave&lt;/h2&gt;

&lt;blockquote class="blockquote"&gt;By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.

&lt;footer class="blockquote-footer"&gt;&lt;cite title="Networking in Compose"&gt;https://docs.docker.com/compose/networking/&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;h2 id="the-solution"&gt;The Solution&lt;/h2&gt;

&lt;p&gt;As of docker-compose 1.21.0:&lt;/p&gt;

&lt;blockquote class="blockquote"&gt;Dashes and underscores in project names are no longer stripped out.

&lt;footer class="blockquote-footer"&gt;&lt;cite title="CHANGELOG 1.21.0 (2018-04-10)"&gt;https://github.com/docker/compose/blob/master/CHANGELOG.md#1210-2018-04-10&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that the &lt;code&gt;docker-compose.yml&lt;/code&gt; file above for &lt;code&gt;bar-service&lt;/code&gt; needs the network name for &lt;code&gt;foo-service&lt;/code&gt; updated to add back in the stripped dashes.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bar-service/docker-compose.yml&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo-service_default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As of Docker Compose file format version 3.5, the network name can be explicitly defined as described in &lt;a href="https://docs.docker.com/compose/networking/#specify-custom-networks"&gt;Specify custom networks&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# foo-service/docker-compose.yml&lt;/span&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.5"&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fooservice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the original external network configuration file for &lt;code&gt;bar-service&lt;/code&gt; would now work, referencing the network of &lt;code&gt;foo-service&lt;/code&gt; as &lt;code&gt;fooservice_default&lt;/code&gt;.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Understanding Clojure: cond and condp</title>
    <link rel="alternate" href="https://matthewboston.com/blog/understanding-clojure-cond-and-condp.html"/>
    <id>https://matthewboston.com/blog/understanding-clojure-cond-and-condp.html</id>
    <published>2016-01-27T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;This is the first part of a multi-part series I want to start titled “Understanding Clojure”. Often times I come across Clojure code that can be refactored a bit better or even there’s an existing function to do &lt;em&gt;X&lt;/em&gt;. Hopefully I can shine some light on some aspects of Clojure you didn’t know existed.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h2 id="cond"&gt;cond&lt;/h2&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cond&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is negative"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is positive"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is zero"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is also the option of a default case using &lt;code&gt;:else&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cond&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is negative"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is positive"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is zero"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="condp"&gt;condp&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;condp&lt;/code&gt; can be used if your predicate remains the same for each condition and you wish to switch on the result.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;condp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"top tier dividend"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"second tier dividend"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"first tier dividend"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"no dividend"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To supply an &lt;em&gt;else&lt;/em&gt; condition to &lt;code&gt;condp&lt;/code&gt;, you leave out the &lt;code&gt;:else&lt;/code&gt; keyword and supply the result expression.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;condp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"top tier dividend"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"second tier dividend"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"first tier dividend"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"no dividend"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also supply &lt;code&gt;condp&lt;/code&gt; with your own predicate function.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cond&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:negative&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:positive&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="no"&gt;:else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:zero&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;condp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:negative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is negative"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:positive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is positive"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your balance is zero"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The predicate function (&lt;code&gt;compare&lt;/code&gt;) must take exactly two arguments and must return a &lt;em&gt;binary result&lt;/em&gt;. The predicate function would be called like:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:negative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:positive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; ]]&gt;</content>
  </entry>
  <entry>
    <title>Datomic Performance with Clause Ordering</title>
    <link rel="alternate" href="https://matthewboston.com/blog/datomic-performance-with-clause-ordering.html"/>
    <id>https://matthewboston.com/blog/datomic-performance-with-clause-ordering.html</id>
    <published>2014-08-13T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;When optimizing datomic queries, it’s best to put the most restrictive clauses first. Doing so, the datomic query engine can perform better.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;For example, say we have 100 cities all with a daily temperature reading for every day of the last year. A total of 36,500 temperature entries. If we want to find the temperature for Denver on July 31, which query would be faster?&lt;/p&gt;

&lt;p&gt;Version 1:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?date&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?date&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Version 2:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?date&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Version 1&lt;/em&gt; will filter 36,500 temperatures → 365 temperatures → 1 temperature.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Version 2&lt;/em&gt; will filter 36,500 temperatures → 100 temperatures → 1 temperature.&lt;/p&gt;

&lt;p&gt;So, I’m suggesting that Version 2 of the query &lt;em&gt;should&lt;/em&gt; perform better per the suggestions of the Datomic &lt;a href="https://docs.datomic.com/on-prem/query.html#clause-order"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="lets-get-scientific"&gt;Let’s Get Scientific&lt;/h2&gt;

&lt;p&gt;Instead of just speculating, how about I run some timings and find out for sure.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version-1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:on&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic.api/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?date&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?date&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version-2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:on&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic.api/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:temperature/city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?city&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sample-1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n-times&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dotimes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n-times&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;version-1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Denver"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clj-time.coerce/to-date&lt;/span&gt;&lt;span class="w"&gt;
                                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clj-time.core/date-time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2014&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;))))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sample-2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n-times&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dotimes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n-times&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;version-2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Denver"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clj-time.coerce/to-date&lt;/span&gt;&lt;span class="w"&gt;
                                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clj-time.core/date-time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2014&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;))))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sample-1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 10.949 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 9.007 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 9.8 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sample-2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 6.772 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 7.103 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 8.745 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It turns out, for a year’s worth of data for 100 cities, the average time for &lt;em&gt;Version 1&lt;/em&gt; was 8.91 msecs. &lt;em&gt;Version 2&lt;/em&gt; average was 7.47 msecs. A measly 1.5 msecs, or 16% difference.&lt;/p&gt;

&lt;h2 id="as-the-dataset-grows"&gt;As the Dataset Grows&lt;/h2&gt;

&lt;p&gt;Let’s think about how these two queries perform as the data grows. After the second year of our application gathering daily temperature data, we would have 73,000 total temperature entries.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Version 1&lt;/em&gt; will filter 73,000 temperatures → 730 temperatures → 1 temperature.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Version 2&lt;/em&gt; will filter 73,000 temperatures → 100 temperatures → 1 temperature.&lt;/p&gt;

&lt;p&gt;Even though the amount of data doubled, the number of candidate entries after the first clause in &lt;em&gt;Version 2&lt;/em&gt; stayed the same.&lt;/p&gt;

&lt;p&gt;How does the increase in data affect our timings?&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sample-1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 42.886 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 30.389 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 43.137 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sample-2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 12.494 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 10.94 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; "Elapsed time: 11.813 msecs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The average for &lt;em&gt;Version 1&lt;/em&gt; for 2 years of data came to 18.87 msecs while the average time for &lt;em&gt;Version 2&lt;/em&gt; was 12.53 msecs. That is nearly 66% the running time. This goes to show, that as your data increases, performance adjustments like these become increasingly important.&lt;/p&gt;

&lt;h2 id="caveat"&gt;Caveat&lt;/h2&gt;

&lt;p&gt;For small datasets, say within the first week, here’s how the two versions perform.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Version 1&lt;/em&gt; will filter 700 temperatures → 7 temperatures → 1 temperature.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Version 2&lt;/em&gt; will filter 700 temperatures → 100 temperatures → 1 temperature.&lt;/p&gt;

&lt;p&gt;Wait, what?&lt;/p&gt;

&lt;p&gt;This is because the number of cities is greater than the number of dates. In this example, it is a small amount and may only result in fractions of a millisecond difference. It shall serve only as a reminder that your data is different than my data, and your results may vary.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>The Avocado Project: Day 1</title>
    <link rel="alternate" href="https://matthewboston.com/blog/the-avocado-project-day-1.html"/>
    <id>https://matthewboston.com/blog/the-avocado-project-day-1.html</id>
    <published>2014-08-01T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;I was inspired to try growing an avocado tree after some new friends mentioned theirs. As I was about to cut open my daily avocado this morning for breakfast, I figured I would give it a shot. I mean, we do have four empty planters on our deck we need to fill.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;Here it is in all its glory just before I’m about to slice it open. A perfectly ripe organic avocado. I’m not sure if the &lt;em&gt;organic&lt;/em&gt; will make a difference for this purpose, but that’s what I had on hand.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Avocado1" src="/images/avocado/80972245989.jpg" class="img-fluid"&gt;
&lt;/figure&gt;

&lt;p&gt;I typically &lt;a href="https://www.youtube.com/watch?v=BmOtULmtVZQ"&gt;use this method&lt;/a&gt; for opening my avocados. But this time I scooped out the seed to keep from damaging it.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Avocado2" src="/images/avocado/93429733365.jpg" class="img-fluid"&gt;
&lt;/figure&gt;

&lt;p&gt;After rinsing and drying the seed, I jammed three bamboo skewers into the widest part of the avocado. It was actually quite a bit easier than I had anticipated. A lot of online instructions say to use toothpicks. I would definitely use toothpicks next time as the thicker skewers slightly split the seed up one side. I hope it’s OK.&lt;/p&gt;

&lt;p&gt;I then placed the Sputnick-looking avocado seed over the top of a wide-mouth mason jar. Be sure to have the pointed end facing up because the root will come out of the bottom. I then filled the mason jar with water just so the bottom of the seed is submerged.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Avocado4" src="/images/avocado/96811082638.jpg" class="img-fluid"&gt;
&lt;/figure&gt;

&lt;p&gt;I would also advise you to, unlike me, jam the toothpicks with a slight upward angle in order to keep the seed just a bit below the rim. This way it is easier to keep the seed submerged without needing to fill the jar clear to the rim.&lt;/p&gt;

&lt;p&gt;Now, place the soon-to-be avocado sprout in a warm, sunny area of your home. Lucky for us, our largest windows face directly east. We get the bright Colorado sunrise coming in every morning!&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Avocado3" src="/images/avocado/52188227241.jpg" class="img-fluid"&gt;
&lt;/figure&gt;

&lt;p&gt;I won’t expect much action out of this little guy for several weeks. In the meantime, I will be keeping an eye on the water level and topping it off to keep the bottom submerged.&lt;/p&gt;

&lt;p&gt;See you in a few weeks!&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Setting Null Values in Datomic</title>
    <link rel="alternate" href="https://matthewboston.com/blog/setting-null-values-in-datomic.html"/>
    <id>https://matthewboston.com/blog/setting-null-values-in-datomic.html</id>
    <published>2014-04-21T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Datomic does not allow the storage of null values. We must reconcile which values were edited and which values were emptied. The emptied values must be a retracted to set to null.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h3 id="why-do-null-values-not-exist-in-datomic"&gt;Why Do Null Values Not Exist in Datomic?&lt;/h3&gt;

&lt;p&gt;It took a few moments to fully understand why it is that Datomic does not allow setting a value to null. If you do try, this is the exception you receive.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IllegalArgumentExceptionInfo :db.error/nil-value Nil is not a legal value  datomic.error/arg (error.clj:55)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looking at the development resources for Datomic and the section on &lt;a href="https://docs.datomic.com/on-prem/acid.html#implications"&gt;Implications&lt;/a&gt;, it states:&lt;/p&gt;

&lt;blockquote class="blockquote"&gt;Most Datomic writes are of tree nodes. These writes are compatible with eventually consistent storage, because the semantics of immutable values are beautifully simple: In an immutable system with no updates, there are only two possibilities:

- a value is present
- a value is not present yet

&lt;footer class="blockquote-footer"&gt;&lt;cite title="Implications"&gt;https://docs.datomic.com/on-prem/acid.html#implications&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important pieces to understand are the bullet points. A value must either exist or not; it cannot be null. Thinking in terms of datoms and entities, an entity either has a value for an attribute or the attribute does not exist for the entity. The entity cannot have an attribute with a value of null.&lt;/p&gt;

&lt;p&gt;So what if we need to set a value to null?&lt;/p&gt;

&lt;h3 id="how-to-set-null-values"&gt;How to Set Null Values&lt;/h3&gt;

&lt;p&gt;Let’s say that John fills out a form and includes his &lt;code&gt;firstname&lt;/code&gt; and &lt;code&gt;lastname&lt;/code&gt;. We would save this to Datomic as such.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/transact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:db/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/tempid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.part/user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Smith"&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can retrieve these values like so.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?lastname&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?firstname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?lastname&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; #{[17592186045418 "John" "Smith"]}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/touch&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17592186045418&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; {:db/id 17592186045418, :user/firstname "John", :user/lastname "Smith"}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now John wishes to remove his &lt;code&gt;lastname&lt;/code&gt; from the system. Instead of setting the value of &lt;code&gt;:user/lastname&lt;/code&gt; to null, we have to &lt;em&gt;retract&lt;/em&gt; the fact of John’s &lt;code&gt;lastname&lt;/code&gt; being “Smith”.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/transact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="no"&gt;:db/retract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17592186045418&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Smith"&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the result of retracting John’s &lt;code&gt;lastname&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; same query as above&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?lastname&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?firstname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?lastname&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; #{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No entities with both the &lt;code&gt;:user/firstname&lt;/code&gt; and &lt;code&gt;:user/lastname&lt;/code&gt; attributes exist.&lt;/p&gt;

&lt;p&gt;However.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?firstname&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?firstname&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; #{[17592186045418 "John"]}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/touch&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17592186045418&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; {:db/id 17592186045418, :user/firstname "John"}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s the same entity but with the &lt;code&gt;:user/lastname&lt;/code&gt; attribute &lt;em&gt;retracted&lt;/em&gt;.&lt;/p&gt;

&lt;h3 id="retracting-values-programmatically"&gt;Retracting Values Programmatically&lt;/h3&gt;

&lt;p&gt;When John submits the form to remove his &lt;code&gt;lastname&lt;/code&gt;, the system can’t hardcode the old value to build the retract statement. Here’s how you might go about finding the correct value and retracting when you need to. This code will not retract values which weren’t removed.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edit-or-create-user-txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Returns a transaction for creating a new entity and/or
  updating attributes of an existing entity."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user-entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;find-user-entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user-entity&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:db/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user-entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;datomic/tempid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.part/user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;edited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;edited-fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;retracted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;retracted-fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user-entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;conj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;retracted&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="n"&gt;user=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;edit-or-create-user-txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:user/middlename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Patrick"&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Smith"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; [{:db/id 17592186045418&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;   :user/firstname "John"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;   :user/middlename "Patrick"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;   :user/lastname "Smith"}]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;user=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;edit-or-create-user-txn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17592186045418&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Johnny"&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:user/middlename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; [{:db/id 17592186045418&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;   :user/firstname "Johnny"}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;  [:db/retract 17592186045418 :user/middlename "Patrick"]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;  [:db/retract 17592186045418 :user/lastname "Smith"]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In order for the above function to work, we need &lt;code&gt;edited-fields&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edited-fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Returns a map of non-empty values to be used as a Datomic entity."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:db/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;second&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="p"&gt;})))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And &lt;code&gt;retracted-fields&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has-empty-value?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keys-for-empty-vals&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Get a list of keys from a map which have empty values."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has-empty-value?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;retract-statement&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Only build a retract statement for existing values."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;old-entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old-entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:db/retract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:db/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old-entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;retracted-fields&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Get all retractions from an existing entity."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;old-entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-entity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filterv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;retract-statement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old-entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;keys-for-empty-vals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-entity&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="n"&gt;user=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;retracted-fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:user/middlename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Patrick"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Smith"&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:user/firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:user/middlename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:user/lastname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; [[:db/retract 17592186045418 :user/middlename "Patrick"]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;  [:db/retract 17592186045418 :user/lastname "Smith"]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a sort of round-a-about way, &lt;code&gt;retracted-fields&lt;/code&gt; returns a vector of &lt;code&gt;:db/retract&lt;/code&gt; vectors for &lt;code&gt;datomic/transact&lt;/code&gt; to handle. It does so using the following method.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Get a list of all keys in the &lt;code&gt;new-entity&lt;/code&gt; which have empty values.&lt;/li&gt;
  &lt;li&gt;Map across them to build a &lt;code&gt;[:db/retract eid key val]&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Filter out retraction vectors which were empty (i.e. no retraction needed).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="write-once-use-everywhere"&gt;Write Once, Use Everywhere&lt;/h3&gt;

&lt;p&gt;The nice thing about the retractions code is it’s been generalized enough that we’ve pulled it into it’s own namespace.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com.example.retractions&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This makes it super simple for the next entity which requires nullable modifications.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;retractions/retracted-fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old-car&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-car&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="resources"&gt;Resources&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://docs.datomic.com/on-prem/acid.html"&gt;Datomic - ACID&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.datomic.com/on-prem/transactions.html"&gt;Datomic - Transactions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Clojure Protocol Namespaces</title>
    <link rel="alternate" href="https://matthewboston.com/blog/clojure-protocol-namespaces.html"/>
    <id>https://matthewboston.com/blog/clojure-protocol-namespaces.html</id>
    <published>2014-03-19T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Importing and requiring Clojure protocols has its tricks. There are some edge cases you must be aware of when using them from separate namespaces.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;First off, if you’re not familiar with &lt;a href="http://clojure.org/protocols"&gt;Protocols&lt;/a&gt;, I suggest you go check out the documentation.&lt;/p&gt;

&lt;p&gt;I’ll continue to use the examples from my recent blog post on &lt;a href="https://matthewboston.com/blog/implementing-clojure-protocols/"&gt;Implementing Clojure Protocols&lt;/a&gt;. Here we have a protocol for defining how something &lt;code&gt;Drives&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;company.drives&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defprotocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"a protocol for driving"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="using-a-protocol-from-a-different-namespace"&gt;Using a Protocol from a Different Namespace&lt;/h3&gt;

&lt;p&gt;In the concrete implementation of &lt;code&gt;Car&lt;/code&gt; we need to require the &lt;code&gt;Drives&lt;/code&gt; protocol like so.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;company.car&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;company.drives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defrecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Spin wheels "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then in the namespace in which we are instantiating and using &lt;code&gt;Car&lt;/code&gt;, we’ll need to require the namespaces of the protocol and record. As well as import the record.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;company.core&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;company.car&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;company.drives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;drive&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;company.car.Car&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;-main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Car.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="what-about-dashes"&gt;What About Dashes?&lt;/h3&gt;

&lt;p&gt;There’s something tricky about using dashes in namespaces that I couldn’t find in any documentation.&lt;/p&gt;

&lt;p&gt;Given these two facts about Java and Clojure:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Java cannot have dashes in class names&lt;/li&gt;
  &lt;li&gt;Clojure converts all dashes to underscores when compiling to Java&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I was able to trial-and-error my way into a solution for dealing with dashes in protocols.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;company.school-bus&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;company.drives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defrecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SchoolBus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"The wheels on the bus go "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;company.core&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;company.car&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;company.drives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;drive&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;company.school-bus&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;company.car.Car&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="n"&gt;company.school_bus.SchoolBus&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;-main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Car.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;SchoolBus.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;45.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice the use of dash in the &lt;code&gt;:require&lt;/code&gt; but the use of underscore in the &lt;code&gt;:import&lt;/code&gt;. This is because the &lt;code&gt;:import&lt;/code&gt; is a Java import used for Java classes, and we must refer to our &lt;code&gt;SchoolBus&lt;/code&gt; record as a Java class.&lt;/p&gt;

&lt;p&gt;Check out my article on &lt;a href="https://matthewboston.com/blog/implementing-clojure-protocols/"&gt;Implementing Clojure Protocols&lt;/a&gt; for more information.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Mixpanel Mock</title>
    <link rel="alternate" href="https://matthewboston.com/blog/mixpanel-mock.html"/>
    <id>https://matthewboston.com/blog/mixpanel-mock.html</id>
    <published>2014-03-12T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Mixpanel is great for tracking and analyzing user behavior within your application. Here, I will show how to mock out the JavaScript agent to keep from sending events during local development.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;When using &lt;a href="https://mixpanel.com"&gt;Mixpanel&lt;/a&gt; it’s best not to send events while in development, staging, demo, or QA environments. I created a class to load a mock that will simply log to the console for tracking events.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MixpanelMock&lt;/span&gt;
  &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mixpanel.track"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mixpanel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MixpanelMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can get the updated source of &lt;a href="https://github.com/bostonaholic/javascripts/blob/master/app/js/mixpanel-mock.coffee"&gt;MixpanelMock on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="using-in-rails"&gt;Using in Rails&lt;/h3&gt;

&lt;p&gt;I usually save the &lt;a href="https://mixpanel.com/help/reference/javascript"&gt;Mixpanel JavaScript Library&lt;/a&gt; to a file in &lt;code&gt;vendor/assets/javascripts/mixpanel.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I place &lt;a href="https://github.com/bostonaholic/javascripts/blob/master/app/js/mixpanel-mock.coffee"&gt;MixpanelMock&lt;/a&gt; in &lt;code&gt;vendor/assets/javascripts/mixpanel-mock.coffee&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I use the code below to conditionally load the real or fake Mixpanel library.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight haml"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"mixpanel"&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"mixpanel-mock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This way, when not in production, all of your Mixpanel tracking events will simply be printed to the console. This also makes it super simple, as a developer, to know when certain events are being sent to Mixpanel.&lt;/p&gt;

 ]]&gt;</content>
  </entry>
  <entry>
    <title>Implementing Clojure Protocols</title>
    <link rel="alternate" href="https://matthewboston.com/blog/implementing-clojure-protocols.html"/>
    <id>https://matthewboston.com/blog/implementing-clojure-protocols.html</id>
    <published>2014-01-30T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Protocols are Clojure’s way of defining Java interfaces. Here, I will show how to define protocols and how to implement them.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;h3 id="how-to-define-a-protocol"&gt;How to Define a Protocol&lt;/h3&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defprotocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"a protocol for driving"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we’ve got a Java interface named &lt;code&gt;Drives&lt;/code&gt; with one (polymorphic) public function &lt;code&gt;drive&lt;/code&gt; to be implemented by &lt;a href="http://clojure.org/datatypes"&gt;deftype or defrecord&lt;/a&gt;. You could also implement the protocol in Java with a little interop magic. I won’t get into this, however.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;drive&lt;/code&gt; function is polymorphic in that it will dispatch on the type of the first argument to the function. This is why every protocol function must take at least one argument. You can think of the first argument as always being &lt;code&gt;this&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id="implement-a-clojure-protocol"&gt;Implement a Clojure Protocol&lt;/h3&gt;

&lt;p&gt;Let’s implement the &lt;code&gt;Drives&lt;/code&gt; protocol and create a concrete &lt;code&gt;Car&lt;/code&gt; class.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defrecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Spin wheels "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Java equivalent:&lt;/p&gt;

&lt;div class="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;Car&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Drives&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;top_speed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Car&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;top_speed&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;top_speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;top_speed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;throttle&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="s"&gt;"Spin wheels "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;top_speed&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="o"&gt;);&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;Now we’ve got a concrete implementation of &lt;code&gt;Drives&lt;/code&gt; named &lt;code&gt;Car&lt;/code&gt;. As you can see, we implemented the &lt;code&gt;drive&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;What’s the &lt;code&gt;_&lt;/code&gt; parameter to &lt;code&gt;drive&lt;/code&gt; you may ask? It essentially means “I don’t care about this value you’re passing in.” It is convention to use &lt;code&gt;_&lt;/code&gt; as the parameter name for values in which your function doesn’t use. In this case, it would be &lt;code&gt;this&lt;/code&gt;, the passed in &lt;code&gt;Car&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;Let’s drive some cars.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ferrari&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Car.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;honda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Car.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;80.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ferrari&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; Spin wheels 100.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;honda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; Spin wheels 80.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With this very scientific and accurate physics model, a Ferrari applying only 50% throttle will drive faster than a Honda with the pedal to the floor.&lt;/p&gt;

&lt;p&gt;So what’d we do? First, we instantiated two instances of &lt;code&gt;Car&lt;/code&gt; by passing in the &lt;code&gt;top-speed&lt;/code&gt; value to the constructor. To call one of the functions from the protocol, you do so just as you would any other clojure function. Passing in the object as the first argument.&lt;/p&gt;

&lt;p&gt;Java equivalent:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Drives&lt;/span&gt; &lt;span class="n"&gt;ferrari&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;Car&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Drives&lt;/span&gt; &lt;span class="n"&gt;honda&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;Car&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;80.0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ferrari&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;drive&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;honda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;drive&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="polymorphic-function-dispatch"&gt;Polymorphic Function Dispatch&lt;/h3&gt;

&lt;p&gt;So we’ve implemented &lt;code&gt;Car&lt;/code&gt; and shown that &lt;code&gt;(drive ferrari 0.5)&lt;/code&gt; works as you would expect. What about another concrete class &lt;code&gt;Boat&lt;/code&gt; which also &lt;code&gt;implements Drives&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defrecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Boat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Drives&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Spin propeller "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;top-speed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chris-craft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Boat.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;70.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chris-craft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; Spin propeller 56.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;drive&lt;/code&gt; function dispatched to the implementation defined by &lt;code&gt;Boat&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id="using-records-and-types-elsewhere"&gt;Using Records and Types Elsewhere&lt;/h3&gt;

&lt;p&gt;Under the covers, the record is essentially a map. This means you can use it just as you would any other map-like data structure.&lt;/p&gt;

&lt;p&gt;Get the &lt;code&gt;top-speed&lt;/code&gt; of the ferrari.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:top-speed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ferrari&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; 200.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Create a &lt;code&gt;blue-honda&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blue-honda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;honda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;;=&amp;gt; Car{:top-speed 80.0, :color "blue"}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; ]]&gt;</content>
  </entry>
  <entry>
    <title>Null Object in Groovy</title>
    <link rel="alternate" href="https://matthewboston.com/blog/null-object-in-groovy.html"/>
    <id>https://matthewboston.com/blog/null-object-in-groovy.html</id>
    <published>2014-01-18T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;When writing tight unit tests, you should use the Null Object Pattern in
order to isolate the method under test. Here is a helper class I would use when
writing unit tests in Groovy.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;If you aren’t familiar with the &lt;a href="http://groovy.codehaus.org/Null+Object+Pattern"&gt;Null Object
Pattern&lt;/a&gt;, I suggest taking a
look now. It’s not a pattern used only in the
&lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt; programming language. In fact, I got the
idea for this blog post while writing RSpec tests at night while simultaneously
working on a Grails application for a client. &lt;a href="https://www.relishapp.com/rspec/rspec-mocks/v/2-6/docs/method-stubs/as-null-object"&gt;RSPec
Mocks&lt;/a&gt;
is where you’ll find my inspiration.&lt;/p&gt;

&lt;p&gt;First, let me show you what the &lt;code&gt;AsNullObject&lt;/code&gt; class looks like.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AsNullObject&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;propertyMissing&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&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="k"&gt;this&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;methodMissing&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&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="k"&gt;this&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;When writing unit tests, you need not care what any collaborator objects do or
(hopefully) their side effects. So, with a little meta-programming magic, no
more &lt;code&gt;MissingMethodException&lt;/code&gt; or &lt;code&gt;MissingPropertyException&lt;/code&gt;. Even better, no
more 15 lines of test setup. Happy dance.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/swanson-dance.gif" alt="Happy dance"&gt;&lt;/p&gt;

&lt;p&gt;Here’s an example.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;groovy&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myNullObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AsNullObject&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="nl"&gt;Result:&lt;/span&gt; &lt;span class="n"&gt;AsNullObject&lt;/span&gt;&lt;span class="nd"&gt;@5a06eeee&lt;/span&gt;

&lt;span class="n"&gt;groovy&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myNullObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="nl"&gt;Result:&lt;/span&gt; &lt;span class="n"&gt;AsNullObject&lt;/span&gt;&lt;span class="nd"&gt;@5a06eeee&lt;/span&gt;

&lt;span class="n"&gt;groovy&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myNullObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;andAnother&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="nl"&gt;Result:&lt;/span&gt; &lt;span class="n"&gt;AsNullObject&lt;/span&gt;&lt;span class="nd"&gt;@5a06eeee&lt;/span&gt;

&lt;span class="n"&gt;groovy&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myNullObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attribute&lt;/span&gt;
&lt;span class="nl"&gt;Result:&lt;/span&gt; &lt;span class="n"&gt;AsNullObject&lt;/span&gt;&lt;span class="nd"&gt;@5a06eeee&lt;/span&gt;

&lt;span class="n"&gt;groovy&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myNullObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;anotherAttribute&lt;/span&gt;
&lt;span class="nl"&gt;Result:&lt;/span&gt; &lt;span class="n"&gt;AsNullObject&lt;/span&gt;&lt;span class="nd"&gt;@5a06eeee&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Whew. Not so bad, eh?&lt;/p&gt;

&lt;p&gt;In the context of a unit test, I would use it like so.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testFireWeapon&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ammo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AsNullObject&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;weapon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BFG&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ammo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;weapon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fire&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"BOOM!"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For our test, we probably don’t want to actually fire any real ammo.  But, we
want our system to behave naturally and not throw errors.&lt;/p&gt;

&lt;p&gt;I hope this helps.&lt;/p&gt;

&lt;p&gt;Cheers.&lt;/p&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>When to Extract Constants</title>
    <link rel="alternate" href="https://matthewboston.com/blog/when-to-extract-constants.html"/>
    <id>https://matthewboston.com/blog/when-to-extract-constants.html</id>
    <published>2013-08-16T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Often times I see programmers get a little “Extract Constant” happy.
Here, I will discuss some of the scenarios in which I believe it is
okay to use constants and when it is &lt;em&gt;not&lt;/em&gt; okay to use constants.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;Consider the following:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Greeter&lt;/span&gt;
  &lt;span class="no"&gt;HELLO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hello"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;HELLO&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a simple and contrived example but you get the point.&lt;/p&gt;

&lt;p&gt;Why extract constants which are merely strings of the same name?
Extracting the &lt;code&gt;HELLO&lt;/code&gt; constant provides no extra information or
insight into the value.&lt;/p&gt;

&lt;p&gt;Understandably, one could argue in favor of reuse. If our greeting
needs to change from “Hello” to “Guten tag”, we have one spot to do
so. But then again, would a constant with the name &lt;code&gt;HELLO&lt;/code&gt; really make
sense now if the value is “Guten tag”? I’m suggesting that it does
not, and everywhere that references the constant &lt;code&gt;HELLO&lt;/code&gt; should be changed to
&lt;code&gt;GUTEN_TAG&lt;/code&gt;; totally nullifying the argument for reuse and maintenance
simplification.&lt;/p&gt;

&lt;h3 id="so-when-should-we-use-constants"&gt;So, when should we use constants?&lt;/h3&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Today&lt;/span&gt;
  &lt;span class="no"&gt;WEDNESDAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wednesday?&lt;/span&gt;
    &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wday&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;WEDNESDAY&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You see, extracting the value 3 into a constant, giving it a name,
helps give some meaning to an otherwise magical 3. Checking for &lt;code&gt;==3&lt;/code&gt;
all throughout our code gives us no insight into what we’re actually
testing. Using the term &lt;code&gt;WEDNESDAY&lt;/code&gt; gives us some context.&lt;/p&gt;

&lt;p&gt;Values, that are constant in the domain, also make great constants.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Earth&lt;/span&gt;
  &lt;span class="no"&gt;DISTANCE_TO_THE_MOON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;238_900&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time_to_the_moon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;DISTANCE_TO_THE_MOON&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;velocity&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="in-summary"&gt;In Summary&lt;/h3&gt;

&lt;p&gt;All in all, here are a few general guidelines I like to follow when
deciding whether or not I should extract a value to a constant:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Code reuse and maintenance simplification&lt;/li&gt;
  &lt;li&gt;Provides a meaningful name to an otherwise “magical” value&lt;/li&gt;
  &lt;li&gt;The value is a fact of the world and never changes&lt;/li&gt;
&lt;/ul&gt;
 ]]&gt;</content>
  </entry>
  <entry>
    <title>Hello World!</title>
    <link rel="alternate" href="https://matthewboston.com/blog/hello-world.html"/>
    <id>https://matthewboston.com/blog/hello-world.html</id>
    <published>2013-07-30T00:00:00+00:00</published>
    <updated>2026-04-22T01:01:40+00:00</updated>
    <summary type="html">&lt;![CDATA[ &lt;p&gt;Hello world! Every engineer’s first program, and now my first blog post.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt; ]]&gt;</summary>
    <content type="html">&lt;![CDATA[ 

&lt;p&gt;I started this blog to write about what I learn and think about as a software engineer. The patterns that work, the ones that don’t, and the opinions I’ve formed along the way about building software that lasts.&lt;/p&gt;

&lt;p&gt;Expect posts about code quality, testing, team dynamics, and whatever else I find worth writing down. If it made me a better engineer, it might be worth sharing.&lt;/p&gt;
 ]]&gt;</content>
  </entry>
</feed>
