<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://iftheshoefritz.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://iftheshoefritz.com/" rel="alternate" type="text/html" /><updated>2026-06-19T15:28:18+00:00</updated><id>http://iftheshoefritz.com/feed.xml</id><title type="html">If The Shoe Fritz</title><subtitle>I like software and I like software books! Books are normally much better researched and thought-out than blog posts, so I thought I&apos;d write some blog posts about books.</subtitle><author><name>Fritz Meissner</name></author><entry><title type="html">AI’s “overnight” solution for our flaky tests took two weeks to adopt</title><link href="http://iftheshoefritz.com/process/ai/artificial%20intelligence/technical%20debt/testing/2026/06/19/ai-overnight-flaky-tests-two-weeks.html" rel="alternate" type="text/html" title="AI’s “overnight” solution for our flaky tests took two weeks to adopt" /><published>2026-06-19T00:00:00+00:00</published><updated>2026-06-19T00:00:00+00:00</updated><id>http://iftheshoefritz.com/process/ai/artificial%20intelligence/technical%20debt/testing/2026/06/19/ai-overnight-flaky-tests-two-weeks</id><content type="html" xml:base="http://iftheshoefritz.com/process/ai/artificial%20intelligence/technical%20debt/testing/2026/06/19/ai-overnight-flaky-tests-two-weeks.html"><![CDATA[<p>Recently I stopped a group of flaky tests from running in CI. 60% of CI runs were failing because of this group, which was unsustainable. Three weeks later I was able to restore that group to CI, with 0% failures on main<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> resulting. Our “non-flaky” tests now give more false positives than the (previously) flaky group.</p>

<p>This is not really a post about tests though, it’s really about AI’s contribution (a lot) and what it took to make that contribution usable (also a lot).</p>

<h2 id="the-hardest-problem">The hardest problem</h2>

<p>Developers on this project had been quarantining tests with a <code class="language-plaintext highlighter-rouge">:flaky</code> label for several years. The strategy was to quarantine a small group which could be expected to fail randomly but could also be re-run easily and separately from the full suite. Apart from the flakiness, the test suite is comprehensive and gives us high confidence that if we merge something after tests pass, it works.</p>

<p>Over the years, several developers had tried for a week at a time to reduce flakiness, all resulting in failure. In our defense, the flaky tests centred around interactive pages using Stimulus or Hotwire, and online discussion of this topic is a combination of ideas we tried already, plus someone saying: “I tried a lot, it doesn’t work, I think there’s a bug”.</p>

<p>The most promising angle was adopting Playwright, which did improve some things but also left us with some tests that failed permanently and needed to be skipped. There’s a dissatisfying way in which this is better than tests that only fail some of the time.</p>

<p>The problem started to look more and more like a trap set for enthusiastic developers. As a manager I always had to urge caution: “sure, you can see some approaches that could help, but bear in mind the last five times anyone tried they found very promising angles that didn’t change the stats in github at all”. Developers whom I trust were seriously recommending deleting the entire group.</p>

<h2 id="opus-solved-it-overnight">Opus “solved it” overnight</h2>

<p>One night, Opus 4.6 running in Claude Code solved “the problem” by running the flaky test group hundreds of times and analyzing failures. There was some prompting to help Claude avoid premature conclusions and be aware that the problems could not be reproduced without repetition, plus a markdown file where it would record progress. Otherwise, no special magic.</p>

<p>I could see Claude’s progress over time because it needed to run the flaky group in larger and larger batches. At first, five times was sufficient because the errors it found occurred 20% of the time. As those were fixed, I had to tell it to use batches of ten, fifty, and then one hundred. Finally, it reached a point where zero errors were found.</p>

<p>A “nice” thing about needing such large batches is that I could leave Claude alone for hours at a time while my normal evening continued. Flaky specs may be a problem uniquely suited to coding agents in that way. There’s not even much token use: it just kicks off a long run and surfaces for an internal conversation, then kicks off the next batch.</p>

<h2 id="two-weeks-to-make-the-results-useful">Two weeks to make the results useful</h2>

<p>This isn’t a post about test failure strategy, so I’ll spare you details of what was flaky and what fixes applied. Instead I’ll try to communicate some of the meta concerns I had with the resulting code changes.</p>

<p>Given a test that looked something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 create objects
2 visit page
3 click A
4 click B
5 expect expression 1 to be true
6 click C
7 expect expression 2 to be true
</code></pre></div></div>

<p>Unchecked, Claude would have turned it into something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1  create objects in a slightly different way that makes no difference
2  visit page
3  explicit sleep
4  unnecessary scoping to a specific section of the page
5    click A
6  end of unnecessary scoping
7  click B, with 3 second wait passed as option arg
8  a clever improvement that should have been on line 3
9  expect expression 1 to be true
10 click C
11 an improvement that worked in other tests but was irrelevant here
12 expect expression 2 to be true
</code></pre></div></div>

<p>Ultimately the changes added up to a good improvement, usually because of one crucial addition per test (in our fictional example, line 8) that was on the wrong line and hidden in a mountain of garbage (lines 3, 4, 6, 7, 11).</p>

<p>It took two weeks to:</p>

<ul>
  <li>separate coincidence from real results</li>
  <li>remove the things that didn’t make a difference</li>
  <li>apply good practice to the important differences</li>
  <li>unify slight variations on the same changes</li>
  <li>generalise to other parts of the test suite</li>
  <li>make sensible commits</li>
</ul>

<p>Some of this work was just a matter of applying good practice (e.g. any explicit sleep call is immediately suspect), and other times it was sending Claude back to hundreds of test runs to prove that something it had added made no difference.</p>

<h2 id="conclusion-processing-my-reactions">Conclusion: processing my reactions</h2>

<p>I see in myself three reactions.</p>

<h3 id="1-hooray-im-still-useful-as-a-programmer">1. Hooray, I’m still useful as a programmer!</h3>

<p>I think it would have been impossible without lots of experience working with Rails and rspec to move from what Claude was suggesting initially towards something sustainable<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. The exact amount of experience necessary is uncertain, but I’m on more than ten years. It took a lot to move beyond the optimism and false positives, and it would have taken more if I didn’t already have a reasonable gut instinct about these things.</p>

<h3 id="2-boy-ai-is-awful-why-bother-with-it-if-it-takes-so-long-to-use-the-results">2. Boy, AI is awful! Why bother with it if it takes so long to use the results?</h3>

<p>I would absolutely use (and recommend) Claude for analysing flaky tests again. I think it would be a mistake not to do so. Accurately running long processes with tiny changes in between multi-hour waits is not a strength for humans.</p>

<p>In addition, Claude did reason through code running in parallel processes in a way that no human had managed for years. That particular part of our code is complex, but has not had active work for years, meaning that no human has good context. Claude probably caught up in 10 minutes.</p>

<p>An interesting aside here is that I find Claude to do much better work when it has tests to help it reason about application code. The tests were flaky, but they were still a good record of what the code was supposed to do.</p>

<h3 id="3-why-keep-going-for-two-weeks-after-ai-clearly-fixed-the-problem-i-care-about-in-one-evening">3. Why keep going for two weeks after AI clearly fixed the problem I care about in one evening?</h3>

<p>I could have taken the win, ignored the cruft, and gained two weeks. If I had, I would have lost those two weeks and more later on. Humans and AI agents would cargo cult the new (anti) patterns, falsely claiming victory over any future flakiness, and making it harder to identify the real problems.</p>

<p>As with all programming, eventually “tidy first, then do the work” ends up being faster than “just do the work”. There’s no escaping the tidying if I want good results, the question is whether I do it at a predictable time and pace or when there’s an emergency (like no-one being able to deploy any code because CI keeps failing).</p>

<p>That includes tidying up after AI.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>commits on main are a proxy for “code that should pass tests”, as opposed to work-in-progress commits, which also go through CI and fail tests for real reasons. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>this was Opus 4.6, but nothing I’ve seen of later versions of Opus gives me confidence that humans are less necessary here. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Fritz Meissner</name></author><category term="process" /><category term="ai" /><category term="artificial intelligence" /><category term="technical debt" /><category term="testing" /><summary type="html"><![CDATA[We lived with flaky tests for years until Claude found a solution overnight. It only took two weeks to make the solution useful.]]></summary></entry><entry><title type="html">Simple, affordable unsupervised agentic coding from my phone with Claude Code in Github Actions</title><link href="http://iftheshoefritz.com/ai/llm/agents/claude/2026/05/04/simple-affordable-agentic-coding.html" rel="alternate" type="text/html" title="Simple, affordable unsupervised agentic coding from my phone with Claude Code in Github Actions" /><published>2026-05-04T00:00:00+00:00</published><updated>2026-05-04T00:00:00+00:00</updated><id>http://iftheshoefritz.com/ai/llm/agents/claude/2026/05/04/simple-affordable-agentic-coding</id><content type="html" xml:base="http://iftheshoefritz.com/ai/llm/agents/claude/2026/05/04/simple-affordable-agentic-coding.html"><![CDATA[<p>If you’re following the AI hype train, you’ll have heard serious software people talk about building software (without needing to code) while they take their kids to the park. This is obviously appealing, but it’s often discussed in ways that imply serious barriers to entry: the interviewee from Anthropic mentions that they get to use all the tokens they want for free, the AI-coding consultancy says that if you’re not spending $1,000 per day per developer you’re doing it wrong, discussions of a new agentic coding paradigm refer to people who switch between multiple top-end Claude Code Max plans.</p>

<p>Although these conversations are frequently intertwined, building software away from a desk doesn’t have to come with a 4-digit daily bill. I don’t have that sort of cash, but I’ve still been able to get in on the fun.</p>

<h3 id="the-20-30month-workflow">The $20-$30/month workflow</h3>

<p>Here’s a workflow that I’ve been using for unsupervised AI coding on a side project which will never justify serious focus or budget:</p>

<ol>
  <li>I type one or two lines into a new issue on the project Github repo (usually from my phone)</li>
  <li>I add a label, <code class="language-plaintext highlighter-rouge">needs-elaboration</code> to the issue</li>
  <li>Github starts up an <a href="https://docs.github.com/en/actions/get-started/understand-github-actions#workflows">actions workflow</a> that invokes Claude Code in the cloud to review the pre-existing code and write a plan for how to turn my few sentences into a reality</li>
  <li>I review the plan and if I’m happy I add a <code class="language-plaintext highlighter-rouge">ready-for-dev</code> label</li>
  <li>Github starts Claude Code to implement the issue and raise a pull request</li>
  <li>I click on preview links that Vercel (this is a NextJS app) adds to the PR and inspect the results</li>
  <li>If I’m happy I merge the PR and Vercel deploys to production</li>
  <li>If I’m not happy I add a comment and an <code class="language-plaintext highlighter-rouge">agent-review</code> label and GOTO 5</li>
</ol>

<p>The total cost is $20 for my Claude Code Pro plan and $10 for a Github Pro Plan (educators and open source contributors can often get this for free). There are usage limits to both plans<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> but I mostly run out of ideas before I run out of limits. I’m not focused on making an agent work 24/7 while I’m doing other things; my only concern is that there is something new ready for me each time I have spare attention for this project.</p>

<p>In case it wasn’t clear, I’m not writing or even reading any of the code as part of this loop<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. That would take focus that I can’t dedicate on a per-feature basis to this project.</p>

<h3 id="minimal-time-and-money-for-decent-results">Minimal time and money for decent results</h3>

<p>This project wasn’t always an experiment in agentic coding. It was originally an exploration of JS web development with NextJS that I pursued during Friday <a href="https://thoughtbot.com/blog/investment-time">investment time</a> over a seven month period a few years back. This allows me to compare before and after, with striking results.</p>

<p>In the past month, with 10 minutes here and there on my phone, I’ve far exceeded what I did in the original seven months of Fridays:</p>

<ul>
  <li>features that have never existed in any previous equivalent software (from myself and others in this niche hobby space)</li>
  <li>huge improvements to UI including working mobile portrait mode and desktop modes</li>
  <li>500+ tests and 77% coverage versus 0 tests and 0% coverage</li>
  <li>optimisations that allowed me to downgrade my Vercel account to a free plan</li>
  <li>GDPR-compliant analytics and error tracking</li>
  <li>extending my integration with Google auth from test-mode (allow-listed users only) to production (anyone can sign up)</li>
</ul>

<p>Not bad for effort spent while out for a walk or on the London underground<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>.</p>

<h3 id="how-it-works-github-workflows-for-cloud-hosted-agentic-coding">How it works: Github workflows for cloud-hosted agentic coding</h3>

<p>When I say “Github starts Claude Code in the cloud” it may not be clear exactly what I mean. Here’s an example of a workflow file from my project’s <code class="language-plaintext highlighter-rouge">.github/workflows</code> folder:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>name: Claude Issue Triage

on:
  issues:
    types: [labeled]

jobs:
  claude-triage:
    if: github.event.label.name == 'needs-elaboration'
    ...
    steps:
      - name: Checkout repository
      ...
      - name: Run Claude Code
        uses: anthropics/claude-code-action@v1.0.70
        with:
          claude_code_oauth_token: $
          prompt: |
            GitHub issue #$ ("$")
            requires your analysis...
        claude_args: "--max-turns 20 --dangerously-skip-permissions"
        show_full_output: "true"
</code></pre></div></div>

<p>Think of it like running a CI workflow in Github actions, except that instead of running against a branch or pull request, this workflow runs when an issue is labelled <code class="language-plaintext highlighter-rouge">needs-elaboration</code>. First it checks out the code, then it runs <a href="https://code.claude.com/docs/en/github-actions">Anthropic’s Claude Code action</a>. The action passes a fixed prompt to Claude Code plus metadata from the Github workflow (like the contents of the labelled issue).</p>

<p>It doesn’t have to be Claude Code, I could go with <a href="https://opencode.ai">Opencode</a> or the Copilot agent instead.</p>

<h3 id="notes-for-people-unfamiliar-with-unsupervised-agentic-coding">Notes for people unfamiliar with unsupervised agentic coding</h3>

<p>If AI-coding terms like <a href="https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04">gastown</a>, <a href="https://www.task-master.dev">taskmaster</a>, <a href="https://ghuntley.com/ralph/">ralph loops</a>, or <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents">minions</a> mean nothing to you, these notes about what becomes different when we stop intervening in agentic code may be helpful.</p>

<p>First of all, the <code class="language-plaintext highlighter-rouge">--dangerously-skip-permissions</code> option on Claude Code may look scary, but it’s standard practice for folks who want to run Claude Code without any human intervention. I am less worried about doing this in a random github actions runner in the cloud than I am running it on my local machine. My laptop remains safe for my day job.</p>

<p>In unsupervised agentic coding, there’s a <a href="https://github.com/ghuntley/how-to-ralph-wiggum?tab=readme-ov-file#-move-outside-the-loop">transition from “human in the loop” (approval of individual code changes that the agent makes) to “human on the loop”</a> where the agent’s output over time is assessed and its standard instructions are improved. I’m not telling Claude which code to modify or how. Instead, it decides and I have to live with the results until I can give it what is needed to solve a problem in a better way the next time. This is a lot like being a manager of human coders (at least one who does not want to be a bottleneck to team output).</p>

<p>Some of the code in this project is really bad, for example a 1500-line JSX file. I only noticed this because of how Claude was struggling to implement changes to this code. Ideally I would have noticed this earlier, but the path to recovery is fairly straightforward. I’ll tell Claude to identify pieces of the component that can be split into separate components and files and have it create new github issues that the agents themselves can plan and implement. This is just how I would work as a developer myself (my threshold for breaking the code down would be somewhat earlier than 1500 lines though).</p>

<p>I wouldn’t trade the progress I’ve made in this month for perfect code. There’s a lightbulb moment for every developer when they work on software that they themselves want to use: suddenly the choice to pursue higher quality code has to fight for development priority with wanting to use a new shiny feature. The delicate balance is in knowing that with too much tech debt (e.g. 1500 line components), the shiny features will take longer and longer to build until eventually “improve quality then build new feature” takes less time than just “build new feature”.</p>

<p>A crucial factor is that the number of lines of code is no longer linked to the time it took to produce them. I often start by asking for options to address a problem. Out of five options, three may be easily rejected. When there are two or more realistic options, I don’t need to reason about them in the abstract: I can have Claude build both and see how each of them feels. There is no opportunity cost choice between one option and another.</p>

<h3 id="validation-from-y-combinator">Validation from Y-Combinator</h3>

<p>The AI coding scene moves fast. As I was writing the first draft of this post I was puzzled that I hadn’t seen anyone else discussing a workflow like this. Between then and now <a href="https://docs.twill.ai/overview">Twill.AI</a> announced a real product which seems like the same idea but with some professional features like more advanced sandboxing and integration with Slack, Whatsapp etc. that they’re betting are worth paying for.</p>

<h3 id="conclusion">Conclusion</h3>

<p>Mine is a very low-stakes project which gives me freedom to experiment. There are serious gaps that I would need to close before applying this workflow to professional work, but I don’t think they’re insurmountable (certainly Twill.AI and many others are trying their best). I expect that over time I’ll be able to extend the agents’ work to close more and more gaps without breaking the bank. In the meantime, you can find me at the park with my daughter.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Your actions will just stop running if either Claude or Github plans run out. There are time-based limits that refresh over different periods. Claude is based on tokens and I use their cheaper tier of models (Sonnet) to make the limit go further. Github Pro Plans include an actions-based limit of 3000 minutes per month. I use much less than 3000 minutes per month on my side project. Github has recently changed their pricing model, but it is mostly with regards to AI token pricing, and in this case I’m getting my tokens directly from Anthropic. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Before you worry about the safety of running code without reading: there is no backend, no personal information tracked, and all data is stored in a user’s browser or in their own Google Drive. Also note that I don’t read code per-feature <em>during</em> the loop, but I do get to look at the code as a whole over time. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>A few weeks back I was watching people play a game that made me think of a new feature. I took out my phone to write an issue, then carried on watching. By the time I was home, the feature was ready in production. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Fritz Meissner</name></author><category term="ai" /><category term="llm" /><category term="agents" /><category term="claude" /><summary type="html"><![CDATA[Podcasts and the blogosphere are awash with people talking about building software while away from their desks, but often burning serious cash in the process. Here's a simple and cheap way to try this out for yourself.]]></summary></entry><entry><title type="html">EuRuKo Refactoring Workshop</title><link href="http://iftheshoefritz.com/conferences/refactoring/2025/09/30/euruko-noisy-animals-workshop.html" rel="alternate" type="text/html" title="EuRuKo Refactoring Workshop" /><published>2025-09-30T00:00:00+00:00</published><updated>2025-09-30T00:00:00+00:00</updated><id>http://iftheshoefritz.com/conferences/refactoring/2025/09/30/euruko-noisy-animals-workshop</id><content type="html" xml:base="http://iftheshoefritz.com/conferences/refactoring/2025/09/30/euruko-noisy-animals-workshop.html"><![CDATA[<p>I gave a workshop at <a href="https://2025.euruko.org">EuRuKo 2025</a> based on the <a href="refactoring/ruby/2025/01/24/introducing-the-noisy-animals-kata.html">noisy animals code kata</a>.</p>

<p>The title was “How to improve your own and others’ code”.</p>

<p>A PDF of my slides is <a href="/files/EuRuKo September 2025 final compressed.pdf">here</a>.</p>

<p>You can find a recording of an earlier version of the workshop that I gave at the <a href="https://lrug.org/meetings/2025/july/">July 2025 London Ruby User Group meeting</a>.</p>]]></content><author><name>Fritz Meissner</name></author><category term="conferences" /><category term="refactoring" /><summary type="html"><![CDATA[My slides from my EuRuKo 2025 refactoring workshop and a reference to an earlier recording. Find out the truth about noisy animals and Hadedahs!]]></summary></entry><entry><title type="html">Watch where you walk by planning experiments</title><link href="http://iftheshoefritz.com/learning/experiments/process/2025/06/05/watch-where-you-walk.html" rel="alternate" type="text/html" title="Watch where you walk by planning experiments" /><published>2025-06-05T00:00:00+00:00</published><updated>2025-06-05T00:00:00+00:00</updated><id>http://iftheshoefritz.com/learning/experiments/process/2025/06/05/watch-where-you-walk</id><content type="html" xml:base="http://iftheshoefritz.com/learning/experiments/process/2025/06/05/watch-where-you-walk.html"><![CDATA[<p>Software delivery plans that consist purely of a list of tasks assume that learning is done before any code is written. This belief is false and leads to software project failure.</p>

<p>Let’s imagine a plan that consists of five milestones. The organisation that wants this work knows far more about the project by the time they reach item three than they did when they wrote the plan. Tackling items three, four, and five exactly as they were specified in the plan is a choice to act based on outdated knowledge.</p>

<h2 id="experiments-choose-your-steps-with-open-eyes">Experiments: choose your steps with open eyes</h2>

<p>There is a better way to plan: we can explicitly acknowledge the vital role of learning by including experiments that we know will influence the plan. Experiments can range from something tiny like swapping between equivalent libraries for pagination, to something really big like trying different distributed architectures.</p>

<p>The benefits of experimenting include:</p>

<ul>
  <li>revealing unanticipated challenges</li>
  <li>moving from theoretical discussion to concrete understanding</li>
  <li>comparing multiple potential solutions in a short space of time</li>
  <li>settling distracting debates between developers</li>
  <li>building knowledge for when implementation begins on a larger scale</li>
</ul>

<h2 id="example-experiments">Example experiments</h2>

<p>Below are some examples of real software-delivery experiments performed as small parts of a larger plan, taken from a variety of projects over the last ten years.</p>

<h3 id="suitability-assessment-by-implementing-elasticsearch-for-one-search-feature">Suitability assessment by implementing Elasticsearch for one search feature</h3>

<p>When a team found SQL-based search to be unforgiving for end users and very slow across multiple tables, Elasticsearch seemed like a solution worth evaluating.</p>

<p>Implementing it on one page was enough to reveal challenges about the infrastructure needed, client libraries, and the knowledge necessary for useful index and query design.</p>

<p>The experiment quickly demonstrated that the trouble to add new infrastructure and learn the necessary skills was greater than the urgency to solve the search problem.</p>

<h3 id="quick-consensus-on-the-performance-impact-of-a-pagination-library-change">Quick consensus on the performance impact of a pagination library change</h3>

<p>Given company-wide adoption of <a href="https://github.com/kaminari/kaminari">Kaminari</a> for pagination in Rails apps and a postgresql performance problem related to pagination, it seemed that the <a href="https://github.com/ddnexus/pagy">pagy</a> gem (which advertises performance benefits over competitors like Kaminari) might help.</p>

<p>Adding it to <em>only</em> the problem page was faster than trying to swap it on every page. This led to the quick discovery that it offered no performance improvements.</p>

<p>The lack of improvement might have been predictable: the problem was related to database query performance, while pagy’s advertised benefits are about the application process and memory usage. However, trying pagy was faster and more convincing than engaging developers in a theoretical conversation.</p>

<h3 id="implementing-small-features-to-assess-suitability-of-a-new-platform">Implementing small features to assess suitability of a new platform</h3>

<p>When one mobile platform became unsuitable due to maintenance concerns, several alternatives were considered. The theoretically most promising option was evaluated by replicating small features in an app for a new platform.</p>

<p>The features were sufficient to prove the benefits the team hoped for (ease of porting existing code) and explore risks like how deployment would change.</p>

<p>Learning happened far faster than if they had attempted to implement every feature in the new platform before thinking about deployment. If the experiment had shown the new platform to be unsuitable, they could have stopped and evaluated other options instead.</p>

<h3 id="safely-building-team-capacity-on-a-new-architecture-by-starting-with-a-small-audience">Safely building team capacity on a new architecture by starting with a small audience</h3>

<p>A Ruby on Rails monolith became overwhelming for a team who needed to act with extreme caution in order to avoid database outages. A distributed architecture with integration via message broker was considered. The team moved clients used by logistics users to the new architecture, but other users such as customers and operations continued to be served by the original architecture.</p>

<p>Lessons from the experiment included how to host the message broker, how to monitor and debug, how it should behave when application processes restarted, and how to integrate with Rails. It was possible to wait until the single audience was well-served by the new API before considering any further adoption of the message broker.</p>

<h3 id="accelerating-lessons-about-a-high-risk-feature-with-a-test-button">Accelerating lessons about a high risk feature with a “test” button</h3>

<p>In a web app a requirement for integration with a bluetooth printer was identified as an early risk. The feature that needed to print was far down the roadmap, but the developer built a “test print” button as one of the first actions in the project. This validated the high risk part of the feature (“print anything via bluetooth”) as early as possible. If the developer had waited to build the entire feature, the lessons from the printing experiment would only have been gained after completion of low risk items like assembling the correct data, UX for initiating printing, or print formatting.</p>

<h2 id="we-cant-afford-not-to-experiment">We can’t afford not to experiment</h2>

<p>Sometimes teams believe that due to a plan with a tight schedule, they have no time to experiment. This is self-defeating. The reality is that risks will materialise, and with experiments a team can face them quickly and with time to adjust. Without experiments, the team faces the same risks, but only when it is too late to do anything about them.</p>

<h2 id="skills-for-successful-experiments">Skills for successful experiments</h2>

<p>This article’s focus is largely on demonstrating the benefits of changing attitudes to planning - plans should be constantly updated based on what we learn during implementation - but there are some skills necessary to make this approach successful.</p>

<p>It’s useful to understand common patterns for experimentation that the tech industry follows, including:</p>

<ul>
  <li><a href="https://thoughtbot.com/blog/break-apart-your-features-into-full-stack-slices">Vertical (or full stack) slices</a>. Planning work as a series of vertical slices is essentially turning the entire product into consecutive experiments, assuming that learning from one slice is applied to the next slice. Possible lessons include saving time by cancelling some slices because they are proven unnecessary by observing the users interacting with previous slices.</li>
  <li><a href="https://bikeshed.thoughtbot.com/414">Spikes</a> are about investigating feasibility before committing to an approach. They are typically short efforts associated with throwaway code.</li>
  <li>Pilot projects imply the testing of solutions on a smaller scale or with a smaller audience (e.g. one team, or one partner organisation) in order to identify lessons earlier than they could if the team waited for a full solution.</li>
  <li><a href="https://thoughtbot.com/blog/solving-the-prototype-puzzle">Prototypes</a> are a representation of a solution in a form that helps decision-makers to learn about their proposal by observing potential use. They are “more real” than a description of the solution, but take less effort than a full implementation.</li>
</ul>

<p>It’s also important to be able to evaluate the success of experiments. Some businesses may have business intelligence teams and tools which can be enlisted for this, but should not become a crutch or bottleneck. Developers outside of those teams (or where no such team exists) can also learn how to <a href="https://thoughtbot.com/blog/lightweight-business-decisions-for-rails-developers">evaluate the impact of their own work</a><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Experiments help us to evaluate risks fast, choose between competing solutions, build consensus, and spread knowledge. They can be as small as a few hours, or take months to demonstrate how to work effectively with a new message broker. Building learning into a plan via experiments is far better than learning the same lessons at a time when it is too late to make different choices.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>the linked article includes practical advice for Ruby on Rails developers, but the mindset it encourages is universal. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Fritz Meissner</name></author><category term="learning" /><category term="experiments" /><category term="process" /><summary type="html"><![CDATA[Developers often follow a plan that assumes that learning is done before they start writing code. This is similar to choosing a destination and then walking with closed eyes. Keep your eyes open by planning experiments instead.]]></summary></entry><entry><title type="html">Falsehoods software teams believe about user feedback</title><link href="http://iftheshoefritz.com/2025/04/03/falsehoods-software-teams-believe-about-user-feedback.html" rel="alternate" type="text/html" title="Falsehoods software teams believe about user feedback" /><published>2025-04-03T00:00:00+00:00</published><updated>2025-04-03T00:00:00+00:00</updated><id>http://iftheshoefritz.com/2025/04/03/falsehoods-software-teams-believe-about-user-feedback</id><content type="html" xml:base="http://iftheshoefritz.com/2025/04/03/falsehoods-software-teams-believe-about-user-feedback.html"><![CDATA[<p>You can skip straight to <a href="#falsehoods-about-user-feedback">the list of falsehoods</a> if you recognise this article’s genre from the title or you’re not someone who likes caveats and explanations.</p>

<p>Software teams often make incorrect assumptions about user feedback. Being aware of how differently one person can interpret something from another’s intent is powerful.</p>

<p>The bulk of this article is a list of many possibly incorrect assumptions. In <em>most</em> cases they are true, but assuming that they are <em>always</em> true leads teams to over- or under-reactions: dismissing a serious issue, or losing productivity in a scramble to meet imagined emergencies.</p>

<p>The list is in the tradition of many other lists of “falsehoods programmers believe”. In the genre a list of common simplifying assumptions is presented with the intention of making the reader aware of edge cases. You can see <a href="https://github.com/kdeldycke/awesome-falsehood/tree/main?tab=readme-ov-file">a collection of similar articles here</a>.</p>

<p>The list below may seem absurd at times, but rest assured that they are based on real life circumstances. The common thread is that people in the detail (software teams) communicate very carefully about their software, and lose sight of communication patterns in other spheres. Insiders assume:</p>

<ul>
  <li>preciseness (e.g. assuming that “some” intentionally conveys “not all”)</li>
  <li>shared context (e.g. knowledge of previous work)</li>
  <li>consistency (e.g. everyone uses the same name for the same feature)</li>
</ul>

<p>… that is not a given in normal communication.</p>

<p>Productive relationships form when there is some give and take where the most involved users start to adopt some of the team’s communication style, and the team learns to more carefully assess communication from those further away. This is doubly true with internal users and layers of management.</p>

<p>And finally, here is the list.</p>

<h3 id="falsehoods-about-user-feedback">Falsehoods about user feedback</h3>

<ul>
  <li>When asked for feedback about a feature, all user responses will be related to that feature</li>
  <li>When multiple users report a problem using the same words, they are all talking about the same problem</li>
  <li>When a user states “there is a problem”, there is only one problem</li>
  <li>When a user states “there are multiple problems”, all of the problems are different</li>
  <li>When a user states “there are multiple problems”, some of the problems are different</li>
  <li>When a user states “X is broken”, they know how X was intended to work</li>
  <li>When a user states “X is broken”, they have used X</li>
  <li>When a user states “X is broken for someone else”, they have seen someone else use X</li>
  <li>When a user states “I heard X is broken”, they heard someone else talking about X</li>
  <li>When a user states “X is broken”, a feature named “X” exists</li>
  <li>When a user states “X is broken”, they are talking about feature X in your software and not other software</li>
  <li>When a user states “X is broken”, X is broken for all cases</li>
  <li>When a user states “sometimes X is broken”, X is not broken in all cases</li>
  <li>When a user states “sometimes X is broken”, they can communicate all problem cases</li>
  <li>When a user states “sometimes X is broken”, they can communicate all problem cases that they’ve seen</li>
  <li>When a user states “sometimes X is broken”, they can communicate any problem cases</li>
  <li>When a user states “sometimes X is broken” but they cannot communicate any cases, X is not broken</li>
  <li>When a user states “X is broken when I do Y”, Y is the only cause of X’s failure</li>
  <li>When a user states “X is broken when I do Y”, Y is the only cause of X’s failure that the user has seen</li>
  <li>When a user states “X is broken again”, the same bug in X is at fault</li>
  <li>When a user states “X is broken again”, they are referring to a problem that stopped and restarted</li>
  <li>When a user states “X is slow”, they know how quickly X was intended to work</li>
  <li>When a user states “X is slow”, they know whether X does eventually work</li>
  <li>When two users state “X is slow”, they both expect the same speed</li>
  <li>When a user states “there is a new bug today”, they are aware of whether the bug existed before today</li>
  <li>When a user states “there is a critical bug”, they are aware of how severe it is relative to other bugs</li>
  <li>When a user states “there is a critical bug”, they are aware of how severe it is relative to other bug reports they have made</li>
  <li>When a user states “there is a critical bug”, they are aware of how many people it impacts</li>
  <li>When an internal user states “there is a critical bug”, they are talking about code in a production release</li>
  <li>When a user states “this bug is urgent”, they are aware of other work that is less urgent</li>
  <li>When two users state “this bug is urgent”, they need a fix in the same time frame</li>
  <li>When asked “is it fixed?” the users will check the software before responding</li>
  <li>When asked “is it fixed?” the users will respond about “it”</li>
  <li>When asked “is it fixed now?” the users will check the release that contains the fix</li>
  <li>When asked “is it fixed now?” the users will respond about experiences that they’ve had more recently than the fix was implemented</li>
  <li>When asked “is it fixed now?” the users will understand that they’re not being asked about an as-yet-unimplemented feature</li>
</ul>

<p>These statements might usually be true, but they are not always true. Ignore this at your peril.</p>]]></content><author><name>Fritz Meissner</name></author><summary type="html"><![CDATA[The feedback we get from users is not what it seems! As software creators we apply a lens that makes us take user feedback in many different - often unhelpful - ways from how it was intended. Here's a list of user feedback myths to help jog assumptions.]]></summary></entry><entry><title type="html">How fast can an ActiveRecord SQL query run?</title><link href="http://iftheshoefritz.com/rails/activerecord/performance/2025/03/31/how-fast-can-an-activerecord-sql-query-run.html" rel="alternate" type="text/html" title="How fast can an ActiveRecord SQL query run?" /><published>2025-03-31T00:00:00+00:00</published><updated>2025-03-31T00:00:00+00:00</updated><id>http://iftheshoefritz.com/rails/activerecord/performance/2025/03/31/how-fast-can-an-activerecord-sql-query-run</id><content type="html" xml:base="http://iftheshoefritz.com/rails/activerecord/performance/2025/03/31/how-fast-can-an-activerecord-sql-query-run.html"><![CDATA[<p>Your database monitoring (perhaps Amazon RDS performance insights, Skylight, or the Postgres <code class="language-plaintext highlighter-rouge">pg_stat_activity</code> view) is telling you that one particular query in your Rails app is causing heavy load. Unfortunately, it already seems pretty fast. What are the odds that you can get it to run any faster?</p>

<p>I had a case where the query generating the heaviest load in an application took 20 milliseconds. It was also the fastest query in the application; some queries in the same application take entire seconds to run! Surely the fastest query in the system had little room for optimisation?</p>

<p>It’s possible to get really good, detailed answers about duration and optimisation strategies for specific queries using <a href="https://thoughtbot.com/blog/reading-an-explain-analyze-query-plan">a database query planner</a>, but sometimes it’s nice to have a rule of thumb that can guide one to a quick answer like: “this query’s duration is [or is not] very close in speed to the fastest my query could possibly run and so I likely therefore can [or cannot] optimise it”.</p>

<blockquote>
  <p>sometimes it’s nice to have a rule of thumb, a quick answer like: “this query’s duration is [or is not] very close in speed to the fastest my query could possibly run and so I likely therefore can [or cannot] optimise it”</p>
</blockquote>

<p>Here’s a trick you can run in the Rails console of your production environment to find out how fast the fastest possible queries can run:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">logger</span> <span class="o">=</span> <span class="no">Logger</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">STDOUT</span><span class="p">)</span> <span class="c1"># assuming you have SQL logging turned off</span>
<span class="o">&gt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="s2">"SELECT 1;"</span><span class="p">)</span>
<span class="no">D</span><span class="p">,</span> <span class="p">[</span><span class="mi">2025</span><span class="o">-</span><span class="mo">03</span><span class="o">-</span><span class="mi">21</span><span class="no">T14</span><span class="p">:</span><span class="mo">03</span><span class="p">:</span><span class="mf">35.765188</span> <span class="c1">#8173] DEBUG -- :    (0.7ms)  select 1;</span>
</code></pre></div></div>

<p>In this snippet we’re turning on ActiveRecord SQL logging (for this Rails console only) as it is normally disabled in production. Then we’re running a query that does not touch any real data. The resulting logging shows that our query takes 0.7ms.</p>

<p><code class="language-plaintext highlighter-rouge">SELECT 1;</code> is the minimum work that can be done in the database<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. The only work that happens is Rails assembling a SQL request, connecting to the database (which is normally on a different server), and returning an answer. Any “real” query must run slower than that because it has to search for and return real data.</p>

<p>It’s interesting to compare this number with the time it takes for a minimal network request to go from application server to database server.</p>

<p>If you have terminal acess to your application server, you could use the <code class="language-plaintext highlighter-rouge">ping</code> utility to get that number. If you’re in the cloud, you might need to rely on sites like <a href="https://www.cloudping.co">cloudping.co (for Amazon)</a> to tell you how long a network round trip takes. The 50th percentile cloudping time within my AWS region is reported to be 1.7ms, so this minimal query is faster than the average round trip time.</p>

<p>This was a real life example: I had previously thought that my 20ms query was pretty much as fast as things could get, but once I realised that a “fastest possible” SQL query could go much faster, I was emboldened to try optimise. I was able to drop the query time drastically: in fact, ActiveRecord now reports that the query takes 1 millisecond <em>or less</em>.</p>

<p>Improving my query time by 20x was a pretty good outcome for a case that started as “I’m not sure if it’s possible to do any better than what I have already”. Thank goodness for learning more about “possible”.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>There is likely a database expert out there who can identify a query that does even less work. The point is that this query definitely does less than any useful query that my application could generate. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Fritz Meissner</name></author><category term="rails" /><category term="activerecord" /><category term="performance" /><summary type="html"><![CDATA[You have a SQL query that seems like it needs to be faster. What's the upper-bound on improving that query's performance?]]></summary></entry><entry><title type="html">ActiveRecord query log tags for GraphQL</title><link href="http://iftheshoefritz.com/activerecord/rails/graphql/2025/03/18/activerecord-query-log-tags-for-graphql.html" rel="alternate" type="text/html" title="ActiveRecord query log tags for GraphQL" /><published>2025-03-18T00:00:00+00:00</published><updated>2025-03-18T00:00:00+00:00</updated><id>http://iftheshoefritz.com/activerecord/rails/graphql/2025/03/18/activerecord-query-log-tags-for-graphql</id><content type="html" xml:base="http://iftheshoefritz.com/activerecord/rails/graphql/2025/03/18/activerecord-query-log-tags-for-graphql.html"><![CDATA[<p>I recently heard of a Rails 7 config option called <code class="language-plaintext highlighter-rouge">config.active_record.query_log_tags_enabled</code>. The <a href="https://api.rubyonrails.org/classes/ActiveRecord/QueryLogs.html">feature</a> adds comments to the SQL queries that Rails generates, like this:</p>

<pre><code class="language-SQL">/*action='index',application='MyApp',controller='myrecords'*/
SELECT "myrecords".* FROM "myrecords" WHERE "myrecords"."user_id" = 200 AND...
/*application='MyApp',job='myrecordjob'*/
SELECT "myrecords".* FROM "myrecords" WHERE "myrecords"."user_id" = 199 AND...
</code></pre>

<p>For readability I’ve split the comments and the queries into separate lines (but they’ll be on a single line in your DB logs) and I’ve turned on <code class="language-plaintext highlighter-rouge">ActiveRecord::QueryLogs.prepend_comment = true</code> so that the comments appear before the query. By default the logs are added at the end of the query.</p>

<p>These comments are visible in Rails application logs, but that’s not very helpful on its own because many production Rails deployments will decrease log levels to the point where ActiveRecord queries are invisible.</p>

<p>More importantly, the comments are actually sent with the SQL queries to my database server, so anything monitoring my queries will also see this metadata.</p>

<p>This is tremendously useful for working with database monitoring like Amazon’s RDS Performance Insights, where I can see what SQL queries are causing the biggest problems, but I can’t identify what application code generated those queries.</p>

<h2 id="improving-on-the-defaults">Improving on the defaults</h2>

<p>By default Rails will log the application, controller, controller action, and (under ActiveJob) job name<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. That’s pretty good for most cases, but many Rails apps generate queries that aren’t usefully described by this data. The two I’ve seen so far are <a href="https://build.betterup.com/adding-sidekiq-job-context-to-activerecord-query-log-tags/">Sidekiq jobs running without ActiveJob</a>, and GraphQL with the <a href="https://graphql-ruby.org">graphql-ruby gem</a>, which will generate lines like this:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*action='execute',application='MyApp',controller='graphql'*/</span>
<span class="k">SELECT</span> <span class="nv">"myrecords"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"myrecords"</span> <span class="k">WHERE</span> <span class="nv">"myrecords"</span><span class="p">.</span><span class="nv">"user_id"</span> <span class="o">=</span> <span class="mi">200</span> <span class="k">AND</span> <span class="nv">"myrecords"</span><span class="p">.</span><span class="nv">"saved"</span> <span class="o">=</span> <span class="k">TRUE</span>
</code></pre></div></div>

<p>No matter what graphQL operation is being executed, the generated SQL queries all receive the same comment, because GraphQL requests offer infinite variety from the same endpoint.</p>

<h2 id="logging-graphql-rubys-resolvers">Logging graphql-ruby’s resolvers</h2>

<p>graphql-ruby offers tracing functionality, which can be enabled like this:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MySchema</span> <span class="o">&lt;</span> <span class="no">GraphQL</span><span class="o">::</span><span class="no">Schema</span>
  <span class="c1"># normal schema stuff</span>
  <span class="n">mutation</span><span class="p">(</span><span class="no">Types</span><span class="o">::</span><span class="no">MutationType</span><span class="p">)</span>
  <span class="n">query</span><span class="p">(</span><span class="no">Types</span><span class="o">::</span><span class="no">QueryType</span><span class="p">)</span>
  <span class="c1"># tell graphql-ruby what code to use for tracing:</span>
  <span class="n">trace_with</span><span class="p">(</span><span class="no">QueryTrace</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The module passed to <code class="language-plaintext highlighter-rouge">trace_with</code> just needs to implement a graphql-ruby lifecycle hook:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">QueryTrace</span>
  <span class="k">def</span> <span class="nf">execute_field</span><span class="p">(</span><span class="o">**</span><span class="n">args</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">args</span><span class="p">[</span><span class="ss">:field</span><span class="p">].</span><span class="nf">resolver</span>
      <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">ExecutionContext</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="ss">graphql_resolver: </span><span class="n">args</span><span class="p">[</span><span class="ss">:field</span><span class="p">].</span><span class="nf">resolver</span><span class="p">)</span>
    <span class="k">end</span>
    <span class="k">super</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>There is some nuance here about which of several GraphQL lifecycle hooks to choose that I’ll <a href="#some-caveats-to-review">discuss later</a>. Now that this data is in <code class="language-plaintext highlighter-rouge">ExecutionContext</code>, I can configure <code class="language-plaintext highlighter-rouge">query_log_tags</code> to use it:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># in config/application.rb</span>
<span class="n">config</span><span class="p">.</span><span class="nf">active_record</span><span class="p">.</span><span class="nf">query_log_tags_enabled</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">config</span><span class="p">.</span><span class="nf">active_record</span><span class="p">.</span><span class="nf">query_log_tags</span> <span class="o">&lt;&lt;</span> <span class="ss">:graphql_resolver</span>
</code></pre></div></div>

<p>Now my SQL queries generated by graphQL operations look like this:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*...,graphql_resolver='Queries::AppVersions'*/</span>
<span class="k">SELECT</span> <span class="nv">"app_versions"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"app_versions"</span>
<span class="cm">/*...,graphql_resolver='Queries::SavedRecords'*/</span>
<span class="k">SELECT</span> <span class="nv">"myrecords"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"myrecords"</span> <span class="k">WHERE</span> <span class="nv">"myrecords"</span><span class="p">.</span><span class="nv">"user_id"</span> <span class="o">=</span> <span class="mi">200</span> <span class="k">AND</span> <span class="nv">"myrecords"</span><span class="p">.</span><span class="nv">"saved"</span> <span class="o">=</span> <span class="k">TRUE</span>
</code></pre></div></div>

<h2 id="some-caveats-to-review">Some caveats to review</h2>

<p>There are some things worth knowing before following this advice.</p>

<h3 id="most-execute_field-invocations-do-not-have-a-resolver">Most execute_field invocations do not have a resolver</h3>

<p>The <code class="language-plaintext highlighter-rouge">QueryTrace#execute_field</code> method is invoked once for every field in a query, but most likely only has an explicit resolver object to log about for root nodes:</p>

<pre><code class="language-gql">query MyQuery {
  savedRecords {  # savedRecords has explicit resolver Queries::SavedRecords
    # id and saved are implicitly resolved
    id
    saved
  }
}
</code></pre>

<p><code class="language-plaintext highlighter-rouge">execute_field</code> will execute once for each field here, but <code class="language-plaintext highlighter-rouge">ActiveSupport::ExecutionContext.set</code> calls are ignored when <code class="language-plaintext highlighter-rouge">nil</code> is passed as a value. With this GraphQL query, I’ll only get a tag for <code class="language-plaintext highlighter-rouge">Queries::SavedRecords</code>, but that should still be enough detail to narrow down my search a lot.</p>

<h3 id="why-not-use-other-hooks-that-are-executed-less-often">Why not use other hooks that are executed less often?</h3>

<p>For what it’s worth, when I enabled this on a GraphQL API that receives 50 queries per second, logging per field did not increased the response times.</p>

<p>If your traffic is much heavier than that, or you’re letting clients send particularly heavy queries, you might want to investigate a lifecycle hook that is invoked less often than <code class="language-plaintext highlighter-rouge">execute_field</code>. <code class="language-plaintext highlighter-rouge">execute_query</code> or <code class="language-plaintext highlighter-rouge">analyze_query</code> (see <a href="https://graphql-ruby.org/api-doc/2.4.11/GraphQL/Tracing/Trace">the documentation</a>) execute only once per query, but here’s why I don’t use them:</p>

<ul>
  <li>The information available at <code class="language-plaintext highlighter-rouge">execute_query</code> time does not include resolvers, so I’d have to log something like <code class="language-plaintext highlighter-rouge">graphql_root: query.selected_query.selections.map(&amp;:name)</code>, which is a lot of graphql-ruby implementation detail to depend on.</li>
  <li>If logging only once for a GraphQL query with multiple roots, all the SQL would be tagged with all of the roots regardless of relevance. The query tag would look like <code class="language-plaintext highlighter-rouge">graphql_root='savedRecords,appVersions'</code>.</li>
</ul>

<h3 id="why-not-just-use-graphqls-integration-with-activesupport-notifications">Why not just use GraphQL’s integration with ActiveSupport notifications?</h3>

<p>GraphQL offers <a href="https://graphql-ruby.org/api-doc/2.4.11/GraphQL/Tracing/ActiveSupportNotificationsTrace.html">GraphQL::Tracing::ActiveSupportNotificationsTrace</a>, which would then allow me to hook into ActiveSupport notifications like <code class="language-plaintext highlighter-rouge">graphql.execute_field</code>, rather than writing my own custom <code class="language-plaintext highlighter-rouge">QueryTrace</code> module. Unfortunately I found the timing of the ActiveSupport notifications to be unreliable, sometimes running after a query completed (and so the SQL query was generated without the added tags).</p>

<h2 id="conclusion">Conclusion</h2>

<p>If I had my way, <code class="language-plaintext highlighter-rouge">active_record.query_log_tags_enabled</code> would be enabled by default in Rails, but I gather that it disables prepared statements, which are important for some applications. For my own applications, adding it is an automatic must, along with the additional configuration necessary for cases like GraphQL or <a href="https://build.betterup.com/adding-sidekiq-job-context-to-activerecord-query-log-tags/">sidekiq</a>.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Recent versions of Rails (7.2 and 8+) also include support <a href="https://github.com/rails/rails/commit/a87668a2388633bffc6c92d46b25b5766776ce4c">for optionally logging :source_location</a> which will output file and line number of the query. I haven’t used this option hence not discussing it here, but I’d view it as complimentary to the logging suggested here rather than a replacement. The linked PR also contains a performance warning about enabling it in production. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Fritz Meissner</name></author><category term="activerecord" /><category term="rails" /><category term="graphql" /><summary type="html"><![CDATA[ActiveRecord query log tags is my pick for most underrated Rails config option that isn't enabled by default. Here's how to extend it beyond the default options, with a specific example of GraphQL.]]></summary></entry><entry><title type="html">Reasons not to refactor</title><link href="http://iftheshoefritz.com/refactoring/good%20code/2025/02/04/reasons-not-to-refactor.html" rel="alternate" type="text/html" title="Reasons not to refactor" /><published>2025-02-04T00:00:00+00:00</published><updated>2025-02-04T00:00:00+00:00</updated><id>http://iftheshoefritz.com/refactoring/good%20code/2025/02/04/reasons-not-to-refactor</id><content type="html" xml:base="http://iftheshoefritz.com/refactoring/good%20code/2025/02/04/reasons-not-to-refactor.html"><![CDATA[<p>Refactoring is a wonderful practice for making code and change easier to manage. That said, here are some reasons why we should not proceed with a refactor.</p>

<h2 id="1-the-change-is-not-really-a-refactor">1. The change is not really a refactor</h2>

<p>Many people use the word “refactoring” incorrectly. If we’re embarking on <a href="/refactoring/good%20code/communication/2025/01/21/what-happens-when-we-misusing-refactoring.html">a change that is not really refactoring</a> (for example looking at a bug or an adjustment after a third party change), we can’t fix it with refactoring.</p>

<p><strong>What to do instead:</strong> we need to think and talk about it differently from refactoring. We can stop and consider how the system will change (from what to what) and raise it with teammates to discuss why it matters, what action to take, and when.</p>

<h2 id="2-there-are-examples-that-dont-fit-our-planned-refactor">2. There are examples that don’t fit our planned refactor</h2>

<p>We often reach a point during refactoring where it seems like there is an easy improvement that applies to almost all cases. It’s usually better not to impose additional abstraction if it only matches “almost” all cases.</p>

<p>Here’s an example inspired by the <a href="https://github.com/thoughtbot/noisy-animals-kata">Noisy Animals kata</a>. Consider this code:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   <span class="k">def</span> <span class="nf">make_noise</span><span class="p">(</span><span class="ss">loud: </span><span class="kp">true</span><span class="p">)</span>
    <span class="n">noise</span> <span class="o">=</span> <span class="p">{</span>
      <span class="s1">'leopard'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">quiet: </span><span class="s1">'growl'</span><span class="p">,</span> <span class="ss">loud: </span><span class="p">[</span><span class="s1">'growl'</span><span class="p">,</span> <span class="s1">'growl'</span><span class="p">]</span> <span class="p">},</span>
      <span class="s1">'owl'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">quiet: </span><span class="s1">'hoot'</span><span class="p">,</span> <span class="ss">loud: </span><span class="p">[</span><span class="s1">'hoot'</span><span class="p">,</span> <span class="s1">'hoot'</span><span class="p">]</span> <span class="p">},</span>
      <span class="s1">'eagle'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">quiet: </span><span class="s1">'caw'</span><span class="p">,</span> <span class="ss">loud: </span><span class="p">[</span><span class="s1">'caw'</span><span class="p">,</span> <span class="s1">'caw'</span><span class="p">]</span> <span class="p">},</span>
      <span class="s1">'mouse'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">quiet: </span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="ss">loud: </span><span class="p">[</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">]</span> <span class="p">},</span>
      <span class="s1">'snake'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">quiet: </span><span class="s1">'slither'</span><span class="p">,</span> <span class="ss">loud: </span><span class="p">[</span><span class="s1">'hiss'</span><span class="p">]</span> <span class="p">},</span>
    <span class="p">}.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">species</span><span class="p">)</span>
    <span class="nb">puts</span> <span class="n">noise</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">loud</span> <span class="p">?</span> <span class="ss">:loud</span> <span class="p">:</span> <span class="ss">:quiet</span><span class="p">)</span>
  <span class="k">end</span>

</code></pre></div></div>

<p>Those repeated strings on some lines might be irritating, but two lines don’t share the repetition. We’re better off leaving the code as is than trying something like this:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">def</span> <span class="nf">make_noise</span><span class="p">(</span><span class="ss">loud: </span><span class="kp">true</span><span class="p">)</span>
    <span class="n">noise</span> <span class="o">=</span> <span class="p">{</span>
      <span class="s1">'leopard'</span> <span class="o">=&gt;</span> <span class="s1">'growl'</span>
      <span class="s1">'owl'</span> <span class="o">=&gt;</span> <span class="s1">'hoot'</span><span class="p">,</span>
      <span class="s1">'eagle'</span> <span class="o">=&gt;</span> <span class="s1">'caw'</span><span class="p">,</span>
      <span class="s1">'mouse'</span> <span class="o">=&gt;</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span>
      <span class="s1">'snake'</span> <span class="o">=&gt;</span> <span class="s1">'slither'</span>
    <span class="p">}[</span><span class="n">species</span><span class="p">]</span>

    <span class="k">if</span> <span class="n">loud</span> <span class="o">&amp;&amp;</span> <span class="n">species</span> <span class="o">==</span> <span class="s1">'snake'</span>
      <span class="nb">puts</span> <span class="s1">'hiss'</span>
    <span class="k">elsif</span> <span class="n">loud</span> <span class="o">&amp;&amp;</span> <span class="n">species</span> <span class="o">==</span> <span class="s1">'mouse'</span>
      <span class="nb">puts</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span>
    <span class="k">elsif</span> <span class="n">loud</span>
      <span class="nb">puts</span> <span class="n">noise</span><span class="p">,</span> <span class="n">noise</span>
    <span class="k">else</span>
      <span class="nb">puts</span> <span class="n">noise</span>
    <span class="k">end</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>Here we’ve moved from a single ternary conditional to an if statement with four branches. Knowledge of how each species behaves was contained in a single line but is now split up over the entire method. The method is also almost double its original length.</p>

<p><strong>What to do instead:</strong> perhaps there is a different code smell we can investigate, or maybe we need to stop where we are until a new requirement demonstrates the need to change.</p>

<h2 id="3-another-refactor-is-already-in-progress">3. Another refactor is already in progress</h2>

<p>We’re often confronted with more than one code smell at a time. Perhaps a piece of code seems like it should be extracted to a method, and inside that code is an unnecessary local variable. Create the method and replace all occurrences of the original code with the method call before you remove the local variable. Alternatively, remove the variable before you extract the code into a method. After you finish one, save, run the tests, and commit, all before you move on to the next.</p>

<p>Whatever you do, don’t try to do both at the same time. Doing them separately may seem like a hassle, but the risk of a typo and the benefit of knowing that you started each change from the safety of green tests are worth much more than the hassle (and there are techniques you can learn to reduce the hassle).</p>

<p><strong>What to do instead:</strong> live with the code that isn’t ideal long enough to see the results of your current refactor. The current refactor may help you realise something new about the code that you would have missed if you tried to do both at the same time.</p>

<h2 id="4-the-code-is-unlikely-to-change">4. The code is unlikely to change</h2>

<p>There’s no benefit to improving code that never changes, even if it is highly complex. This is the basis of Michael Feather’s <a href="https://www.stickyminds.com/article/getting-empirical-about-refactoring">process for identifying good refactoring targets</a> (spoiler: code that is complex and changes often is the best place to look).</p>

<p>We should be aware in this case that it is really easy to apply wishful thinking: we imagine that the code that everyone is scared of will never change, simply because we cannot think of how to improve it.</p>

<p><strong>What to do instead:</strong> find something else that is more likely to change to refactor, or pause refactoring and move on to the next requirement.</p>

<h2 id="5-there-are-no-tests">5. There are no tests</h2>

<p>If we’re changing tests (or we have none), we have no guarantee that behaviour is unchanged. If we are possibly changing behaviour, we are not refactoring.</p>

<p>Here’s a delightful extract from <a href="https://app.thestorygraph.com/books/4795b19b-f913-44c2-a44b-73af56931c3d">Refactoring Javascript</a>:</p>

<blockquote>
  <p>HOW CONVERSATIONS ABOUT REFACTORING SHOULD GO UNTIL TESTS ARE WRITTEN</p>

  <p>“I refactored login to take email address and username.”</p>

  <p>“No, you didn’t.”</p>

  <p>“I’m refactoring the code to <em>__</em>”</p>

  <p>“No, you aren’t.”</p>

  <p>“Before we can add tests, we need to refactor.”</p>

  <p>“No.”</p>

  <p>“Refactoring th–”</p>

  <p>“No.”</p>

  <p>“Refa–”</p>

  <p>“No.”</p>
</blockquote>

<p><strong>What to do instead:</strong> Instead of “refactoring” without tests, we can start by writing some tests! <a href="https://thoughtbot.com/blog/unlock-the-value-of-tests-in-understanding-your-codebase">Writing new tests will help us learn about the code</a>. After that, we can come back to refactoring.</p>

<h2 id="6-refactoring-reveals-more-risk-than-imagined">6. Refactoring reveals more risk than imagined</h2>

<p>Sometimes we tidy the code to a certain point and realise that there is something we don’t understand. That’s ok, we can stop where we are now. If we’ve been working in small low-risk steps the way that the refactoring community encourages, the tidying we did already was either valuable and should be kept, or is really safe and easy to revert.</p>

<p>From <a href="https://martinfowler.com/books/refactoring.html">Martin Fowler’s description of the Refactoring book</a>: “its essence is a series of small behavior-preserving transformations… You also avoid having the system broken while you are carrying out the restructuring - which allows you to gradually refactor a system over an extended period of time.”</p>

<p><strong>What to do instead:</strong> Simply pause where we are and continue another time when we know more. That “more” may be something about our product that reveals that some code is overly complex or unnecessary. It might also be a new requirement that shows us a new way that the code is likely to change in future.</p>

<h1 id="this-might-lead-to-more-refactoring">This might lead to more refactoring</h1>

<p>We’ve discussed several reasons not to refactor. Ironically, we might end up doing more refactoring as a result, because we won’t be trying to use refactoring to solve unsuitable problems. The refactoring we do will be faster, have a bigger impact, and be more rewarding.</p>]]></content><author><name>Fritz Meissner</name></author><category term="refactoring" /><category term="good code" /><summary type="html"><![CDATA[Refactoring is great and most developers should refactor more, but there are times when it is not appropriate. Ironically, if you're aware of these, you may find yourself refactoring even more often than before.]]></summary></entry><entry><title type="html">Introducing the Noisy Animals kata</title><link href="http://iftheshoefritz.com/refactoring/ruby/2025/01/24/introducing-the-noisy-animals-kata.html" rel="alternate" type="text/html" title="Introducing the Noisy Animals kata" /><published>2025-01-24T00:00:00+00:00</published><updated>2025-01-24T00:00:00+00:00</updated><id>http://iftheshoefritz.com/refactoring/ruby/2025/01/24/introducing-the-noisy-animals-kata</id><content type="html" xml:base="http://iftheshoefritz.com/refactoring/ruby/2025/01/24/introducing-the-noisy-animals-kata.html"><![CDATA[<p>I created a code kata! The <a href="https://github.com/thoughtbot/noisy-animals-kata/">Noisy Animals code kata</a> is a programming exercise designed to teach principles of improving bad code. The exercise consists of:</p>

<ul>
  <li>around one page of bad code that generates animal noises (dogs go “woof”, cats go “meow”, etc)</li>
  <li>tests that verify the behaviour of the code for every supported animal</li>
  <li>some reflection questions for before and after attempting to improve the code</li>
  <li>some new requirements to be implemented that affect how the reader might want to organise the code</li>
  <li>hints for making progress when stuck</li>
  <li>discussion of ideas in the refactoring community</li>
</ul>

<p>It is available in Ruby and Javascript. The code and instructions are all in the <a href="https://github.com/thoughtbot/noisy-animals-kata/">github repository</a>.</p>

<h1 id="inspiration">Inspiration</h1>

<p>The exercise owes a substantial debt of inspiration to the exercise and ideas in Sandi Metz and Katrina Owen’s <a href="https://sandimetz.com/99bottles">99 Bottles of OOP</a>, as well as to the <a href="https://github.com/NotMyself/GildedRose">Gilded Rose kata</a>.</p>

<h1 id="relevance-to-professional-programming">Relevance to professional programming</h1>

<p>When code is continuously updated with only the smallest change possible and never reorganised, it often degrades to a level equivalent to (or worse than) this exercise’s starting point.</p>

<p>Although the example is small and the domain of animal noises trivial, the form of the code and particularly the techniques for improving it are very relevant to professional code.</p>

<h1 id="why-another-exercise">Why another exercise?</h1>

<p>99 Bottles is an amazing book and this exercise is intended to expose some of its ideas (and those of the refactoring community in general) in a shorter form that nudges one towards refactoring without assuming the reader has ever heard of the concept.</p>

<p>99 Bottles is itself very approachable for beginners, but it tends to only be read and discussed by people who are already in (or are connected to others) in the refactoring community.</p>

<p>The Gilded Rose Kata is a wonderful exercise, but its World of Warcraft domain is less and less approachable with the passage of time, even in the tech community.</p>

<h1 id="contributions-welcome">Contributions welcome</h1>

<p>The exercise can no doubt be improved. If you think something about the exercise is less than ideal or you have a new idea for it, you’re welcome to open an issue or a pull request <a href="https://github.com/thoughtbot/noisy-animals-kata/">on the repository</a>.</p>]]></content><author><name>Fritz Meissner</name></author><category term="refactoring" /><category term="ruby" /><summary type="html"><![CDATA[The Noisy Animals kata is designed to help developers think about the process of improving bad code.]]></summary></entry><entry><title type="html">What happens when we misuse refactoring in conversation?</title><link href="http://iftheshoefritz.com/refactoring/good%20code/communication/2025/01/21/what-happens-when-we-misusing-refactoring.html" rel="alternate" type="text/html" title="What happens when we misuse refactoring in conversation?" /><published>2025-01-21T00:00:00+00:00</published><updated>2025-01-21T00:00:00+00:00</updated><id>http://iftheshoefritz.com/refactoring/good%20code/communication/2025/01/21/what-happens-when-we-misusing-refactoring</id><content type="html" xml:base="http://iftheshoefritz.com/refactoring/good%20code/communication/2025/01/21/what-happens-when-we-misusing-refactoring.html"><![CDATA[<p>In a 2017 post (on thoughtbot.com where this post originally appeared) German Velasco wrote about <a href="https://thoughtbot.com/blog/lets-not-misuse-refactoring">how to think about and do refactoring right</a>. This is a follow-up post about what goes wrong when we misuse refactoring and (in German’s words) “rob ourselves of the power to communicate an important concept”.</p>

<p>Done well, refactoring is a low-risk investment in future productivity, by reorganising code without changing its behaviour. Often however, our conversations position work that doesn’t meet this description - and needs more scrutiny - as refactoring. Let’s look at what goes wrong when this happens.</p>

<p>As an aid to conversation, let’s start with some examples of misuse (based on real team conversations):</p>

<ul>
  <li>“refactor” a mobile app component so that it handles disabled device location (this was not refactoring, it was a bug fix)</li>
  <li>“refactor” to v2 of a third party web API (supporting v2 would require lots of change)</li>
  <li>“refactor” for performance (we want a significant improvement - or why bother - meaning that the code behaves differently)</li>
  <li>“refactor” from synchronous to asynchronous communication (the users would get the same end result, but the code and their experience would be very different)</li>
</ul>

<p>All of these conversations described important work that I don’t mean to discourage. Still, none of them was really about refactoring. This use of the word “refactoring” seems to signal that the speaker is thinking technically, but not about what refactoring really implies.</p>

<h2 id="misusing-refactoring-sabotages-important-conversations">Misusing “refactoring” sabotages important conversations</h2>

<p>Refactoring is a technical word typically describing a series of quick, well-understood, easily reverted changes that a single developer (or pair) makes. <a href="https://thoughtbot.com/blog/lets-not-misuse-refactoring">German’s post</a> describes this really well.</p>

<p>Misusing refactoring to describe work that doesn’t fit that description avoids important conversations about:</p>

<ul>
  <li><strong>Prioritisation:</strong> the product will change, but there’s no conversation about whether the product needs the change, or why now. Higher priority work slows down or even becomes blocked.</li>
  <li><strong>Scope:</strong> the change is not well understood, but “refactoring” implies that there is no change worth discussing. We’re not defining success, more and more cases feel like they need to be addressed immediately, and the work never ends.</li>
  <li><strong>Collaboration:</strong> without conversation about the necessary skills and context (or who would help), we produce bad (or scrapped) code.</li>
  <li><strong>Capacity:</strong> refactoring implies that this is a small piece of work, but since we’re not really refactoring we could be taking on a large task that the team cannot accomodate.</li>
</ul>

<h2 id="successive-failures-lead-the-team-to-avoid-real-refactoring">Successive failures lead the team to avoid (real) refactoring</h2>

<p>After a while, long periods of work with no result creates distrust. People learn to stop listening (or become hostile) when “refactoring” comes up. That increases the chance of failure even further, because real refactoring is a powerful tool, and teams lose out when they dismiss it.</p>

<h2 id="restoring-faith-in-real-refactoring">Restoring faith in real refactoring</h2>

<p>Ideally we would be successful at both refactoring and other kinds of change. Below are two ideas that can help.</p>

<h3 id="find-more-specific-words-for-non-refactoring-work">Find more specific words for non-refactoring work</h3>

<p>Remembering that the examples above are not refactoring but are still important work, here is some improved wording for a better conversation:</p>

<ul>
  <li>fix the bug where my component doesn’t handle disabled device location</li>
  <li>migrate to v2 of the third party API because v1 is being deprecated by its authors</li>
  <li>improve order read performance by 30%</li>
  <li>change to asynchronous communication to handle cases where the client and the server get disconnected</li>
</ul>

<p>You may notice that there is more detail in the reworded examples. That’s intentional. When we stop using “refactoring” with an audience who don’t understand the word, we’re more likely to get into helpful specifics. A listener is more likely to ask: “why do I need this change and not something else?”, or “what does success look like?” when meaning is not hidden.</p>

<h3 id="learn-more-about-refactoring">Learn more about refactoring</h3>

<p>If we understand refactoring correctly, we’re not going to misuse it in conversation, and we stand more chance of success when we do need to refactor. Experience has taught me that refactoring without proper understanding is very likely to fail. For this I’ll point you for a third time to <a href="https://thoughtbot.com/blog/lets-not-misuse-refactoring">German’s blog post</a>; for further reading it’s worth going into the books he cites.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We need to be careful not to claim that we’re refactoring when we’re really making bug fixes, adding new features, or otherwise changing the code’s behaviour and our product’s user experience. Disguising that work as refactoring sabotages important conversations and creates suspicion about the concept. We can fix this by getting more specific in our language and by learning more about what refactoring really is.</p>]]></content><author><name>Fritz Meissner</name></author><category term="refactoring" /><category term="good code" /><category term="communication" /><summary type="html"><![CDATA[Refactoring is essential for software teams to remain effective, but the concept is often misused in conversation to describe work that is not refactoring. Here are some thoughts on what goes wrong when we do this.]]></summary></entry></feed>