<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[onoffswitch.net]]></title><description><![CDATA[Ramblings, musings, seg faults, and type systems. 10+ years of (mostly) tech mumbo jumbo]]></description><link>https://www.onoffswitch.net</link><image><url>https://substackcdn.com/image/fetch/$s_!PZK6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4cb7844-4d27-476c-bb70-6fe47f478538_256x256.png</url><title>onoffswitch.net</title><link>https://www.onoffswitch.net</link></image><generator>Substack</generator><lastBuildDate>Mon, 27 Apr 2026 09:31:04 GMT</lastBuildDate><atom:link href="https://www.onoffswitch.net/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Anton Kropp]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[onoffswitch@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[onoffswitch@substack.com]]></itunes:email><itunes:name><![CDATA[Anton Kropp]]></itunes:name></itunes:owner><itunes:author><![CDATA[Anton Kropp]]></itunes:author><googleplay:owner><![CDATA[onoffswitch@substack.com]]></googleplay:owner><googleplay:email><![CDATA[onoffswitch@substack.com]]></googleplay:email><googleplay:author><![CDATA[Anton Kropp]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Oh Boy AI]]></title><description><![CDATA[the thing we're all talking about]]></description><link>https://www.onoffswitch.net/p/oh-boy-ai</link><guid isPermaLink="false">https://www.onoffswitch.net/p/oh-boy-ai</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Mon, 25 Aug 2025 15:00:38 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HUDf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve had a lot, and I mean a lot, of discussion about AI in the past 3-6 months.  It&#8217;s been a bit of an emotional rollercoaster, from &#8220;hey cool this is neat tech&#8221; to &#8220;omg is my entire profession going to be wiped out&#8221; to &#8220;it&#8217;s another tool in the box&#8221;.  Like with all new technologies AI is going through the hype cycle as people find what works, how it incorporates into products and day to day work, and settles into the new normal.</p><p>For those who lived through cloud, mobile, and crypto, this all feels really familiar. But neither Cloud nor mobile nor crytpo went away. They just sort of found their place in the world and they&#8217;re now standard in a lot of industries and workflows.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I wanted to put some thoughts down about AI, how I&#8217;m using it, and how I think organizations should be embracing it.  </p><h1>AI For The Individual Contributor</h1><p>For the IC AI is both amazing and horrifying.  On the one hand, it can spit out an insane amount of mostly workable code. On the other hand, it can spit out an insane amount of mostly workable code.</p><p>When I first started to play with AI I was throwing it at ambiguous problems and then watching it hallucinate and spiral out of control. And while that&#8217;s kind of fun and hilarious, it put a bad taste in my mouth about the first interaction with it.  On top of that, as an IC I am drawn to deterministic outputs and LLMs are just not that! Chaotic systems are those that given a very slightly different variance in input give wildly different outputs.  AI isn&#8217;t really that, but sort of does feel that way.  It was challenging to find a reasonable way to use it.</p><p>I&#8217;ve been using CoPilot for a long time, and I quite liked the small context sensitive suggestions for boilerplate I didn&#8217;t want to write. I probably would have killed for CoPilot when I was at Stripe working in Go before generics. There&#8217;s only so many for loops to map data from A to B I can write in my life and I&#8217;ve long exceeded that amount.  </p><p>Incorporating something like Claude into my workflow has been a learning curve.  What I&#8217;ve come away with is it&#8217;s great, for me, in two scenarios:</p><h2>Discovery Engine</h2><p>LLMs are a fantastic way to explore new topics using NLP as a human interface.  I mean thats what LLMs are best at: NLP (natural language processing).  I often ask Claude about syntax I don&#8217;t know, topics I am learning about, and as a general entrypoint into knowledge systems.  </p><p>But, and this is the big but, probing ends mostly there.  After that I will often try and go to primary documents or other vetted knowledge bases to dig further. LLMs are prone to giving close-but-not-quite answers but that&#8217;s kind of OK if you&#8217;re trying to frame a mental picture and squint at a fuzzy thing to understand its shape. </p><p>I&#8217;ve taken to using LLM entrypoints as a first pass for the unknown.  The moment I encounter things that are clearly wrong or it&#8217;s starting to break down I give up on it. It&#8217;s not time well spent to try and get an LLM to give you an answer you want. That&#8217;s signal that you don&#8217;t know enough to ask or that the information is more nuanced and its back to using your grey matter to figure it out.</p><p>LLMs can be an amazing way to facilitate learning and learning faster. But you can&#8217;t short circuit actual learning, even if the LLM looks like it can do all the work for you. </p><p>As an example of this in a non technical area, my partner and I recently bare boat chartered in Croatia and sailed the Adriatic for a week.  We&#8217;ve been sailing for 7 years and done a lot of long trips here in the PNW and into the Canadian Gulf Islands, so we&#8217;re not strangers to life on a boat. But this was the first time we were planning an overseas adventure. As part of planning I spent a lot of time poring over charts, pilot books, and forums trying to piece together routes, anchorages, and provisioning areas. I was feeling overwhelmed by all the new information and it was hard to figure out what was a good starting point.  To help narrow the focus down I opted to try ChatGPT to help me out and at first glance it pulled some great information. I asked it to get me context on prevailing winds, backup routes, weather forecasts, all sorts of stuff I have to do when planning a trip. </p><p>While I was impressed with what it came up with it, a lot of it turned out to be nonsense.  However, it did give me a great starting point for further analysis and it helped frame my mental model of what was common in the area.  When I finally got the plan together it was nothing like what the LLM gave me, but I think this is a good example of using LLMs as discovery engines and probing through ambiguities.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HUDf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HUDf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HUDf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HUDf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HUDf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HUDf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HUDf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HUDf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HUDf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HUDf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2f03e45-d677-49da-9a29-868f82061a21_2490x1868.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Enjoying the Adriatic weather at the helm of our 35&#8217; sailboat</figcaption></figure></div><h2>Boilerplate Code Monkey</h2><p>LLMs are pretty good at writing code, and a lot of it. However, most code in the wild is absolutely fucking awful and lines of code written is probably one of the <a href="https://getdx.com/blog/lines-of-code/">worst</a> metrics you can ever include as a measure of success.  Anyone can write poorly factored, poorly encapsulated, poor architected code. I should know, I spent years doing it as a junior engineer! Well written code is an art and requires deep understanding of the direction you&#8217;re going, the systems you&#8217;re in, and the trade off&#8217;s you&#8217;re making.</p><p>I&#8217;ve had a lot of success using LLMs to do semi-complex work when I direct them with clear examples and the codebase I&#8217;m in is resilient to these kinds of changes.  What I mean is that code in the codebase is consistent, has lots of good tests and test fixtures, linting, and obviously easy to consume patterns.  </p><p>In areas of code that are more chaotic, the LLM creates more chaos.  Left to its own devices it creates tests that are mocking everything and testing effectively nothing, imagining endpoints that don&#8217;t need to exist, special casing workflows, duplicating code it doesn&#8217;t know exists already, etc.</p><p>Recently I had a Claude interaction that was very successful for me:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FstC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FstC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 424w, https://substackcdn.com/image/fetch/$s_!FstC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 848w, https://substackcdn.com/image/fetch/$s_!FstC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 1272w, https://substackcdn.com/image/fetch/$s_!FstC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FstC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png" width="1456" height="705" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:705,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:237210,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.onoffswitch.net/i/171699726?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FstC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 424w, https://substackcdn.com/image/fetch/$s_!FstC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 848w, https://substackcdn.com/image/fetch/$s_!FstC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 1272w, https://substackcdn.com/image/fetch/$s_!FstC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F459642d4-b6aa-43a6-8087-222ea83fad96_1982x960.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I like these kinds of tasks for an agent. They are reasonably scoped, annoying for a human to automate, and move the needle in a net positive way.  However I also at some point tried to use it for a task that would touch hundreds of files and it broke down really quickly.</p><p>I know it&#8217;s possible to get LLMs to do <em>more</em> but the cost of guiding and reviewing their work isn&#8217;t worth it to me.  In fact I think most people suck at explaining what they want.  I&#8217;ve read enough JIRA tickets to know that even knowing the final outcome of an ask is often ambiguous.</p><p>I am probably not going to be someone who spends ages customizing agent workflows, creating ultra detailed <code>CLAUDE.md </code>files, etc.  Just like I don&#8217;t spend a lot of time making <code>vimrc</code> files.  I firmly believe tools should be easy to use, after all the easy thing should be the right thing.  I&#8217;m too lazy I guess.  That said, this kind of work, like showcased above is tedious, painful, and I just hate it. Having the LLM do it is actually a massive time saver but it was successful because <em>I did the example that it follows myself</em>.  This is kind of no different to how I&#8217;d lead a team of people, good patterns propagate and bad patterns propagate too.</p><p>It&#8217;s critical though that I review all of it&#8217;s output and validate that it does the right thing. If I let it write code that I don&#8217;t understand then it&#8217;s ralph-meme territory.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RpaA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RpaA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 424w, https://substackcdn.com/image/fetch/$s_!RpaA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 848w, https://substackcdn.com/image/fetch/$s_!RpaA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!RpaA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RpaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;I'm in Danger': The Evolution of a One-Liner Into a Viral Meme&quot;,&quot;title&quot;:&quot;I'm in Danger': The Evolution of a One-Liner Into a Viral Meme&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="I'm in Danger': The Evolution of a One-Liner Into a Viral Meme" title="I'm in Danger': The Evolution of a One-Liner Into a Viral Meme" srcset="https://substackcdn.com/image/fetch/$s_!RpaA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 424w, https://substackcdn.com/image/fetch/$s_!RpaA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 848w, https://substackcdn.com/image/fetch/$s_!RpaA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!RpaA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83e97d8a-d63e-45cf-9f16-3ab7d85c2057_2000x1000.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><h3>Pitfalls Of Vibing</h3><p>Letting LLM&#8217;s vibe their way through code is becoming such an issue that new patterns of code engagement are being explored, like knowing if <a href="https://github.com/ghostty-org/ghostty/pull/8289">LLMs were used to create code</a>.  It makes sense, if you&#8217;re reviewing things it does actually matter if someone thought the code through or not.  Even if you use an LLM did you actually review it with the same rigor as if you wrote it by hand?  I can tell you for sure if I see a PR with 1k+ changes I&#8217;m not reading any of that too carefully and I trust a coworker has thought it all through or we&#8217;re going to go back to the drawing board.</p><p>It&#8217;s funny to think that code review as a skill is more important than ever now, and many people struggle with reviewing code. It&#8217;s hard, and it takes years to practice. Often code review as a skill only happens when you have a mental model to map things to. I expect code to follow specific shapes, patterns, and similarities.  That comes with lots of experience and practice though.</p><p>In any setting, reviewing thousands of lines of effectively auto generated code is never going to happen.  How many of us have scaffolded a project using some new framework and just &#175;\_(&#12484;)_/&#175;&#8217;d our way through the generated code. We&#8217;ve learned a long time ago that small changes are easier to follow, keeping cognitive load low and minimizing bugs. </p><p>As IC&#8217;s we should be using LLMs in the same way  and free ourselves up to focus on the important things. Things like setting up codebases and systems for success.  If you don&#8217;t know what success is your LLM agents will run rampant making their own decisions for better or worse.   </p><p>I recently spent some time in a heavily LLM coded codebase and very quickly found inconsistencies that actually slowed me down and led my own agent use down rabbit holes that created more issues than they actually fixed. I had to pull back, refactor, and backtrack to undo a lot of cruft that worked but wasn&#8217;t extensible.  I was tempted by the vibe-dragon and it bit back.</p><h1>AI In Products</h1><p>Just about every company in the world is trying to figure out how to incorporate LLMs into their product, not just their employee workflows.</p><p>Lot of new products are coming out that are 100% LLM driven and it will be interesting to see how many of those succeed over time.  These companies are leveraging RAG, LangChain, and in general finding ways to break down their problem domain into smaller sub-contexts to have agents excel at and then chain them together to create more interesting flows.   </p><p>I think using NLP as an interface is really cool and having a Claude style prompt in scenarios where complex onboarding occurs is actually slick as hell.  Using LLMs as tiny &#8220;nice to haves&#8221; in places is a joy. Some good examples are places in systems that ask for some sort of textual boilerplate: &#8220;title&#8221; or &#8220;description&#8221; are classic examples.  Let some AI slop auto gen that based on other localized context and that&#8217;s a loveable moment in any product.  I&#8217;d be surprised if this wasn&#8217;t even baked into new UI frameworks honestly.  I worked on an NLP system in 2017 trying to categorize text into groups and it&#8217;s funny that we built hand rolled models to do this when an LLM excels super well at this.</p><p>However there is a limit to how much people want to interact with a non deterministic engine that starts to hallucinate over time.  Eventually people want determinism and control.  Adding LLMs on systems that already have well defined APIs and SDKs is a great way to gracefully extend products. Building escape hatches is critical. I almost feel like the escape hatch should be the first way you build things - SDK and API first, and then let LLM&#8217;s drive using structured schemas and MCP tools as constraints to your system.</p><p>I see this the same way mobile happened.  Do you like it when a company ONLY has a mobile app? It can be nice to have a desktop app, or even an in person moment (depending on industry).  It&#8217;s great when it has a mobile app, but forcing people into specific workflows often kind of sucks.</p><p>Customer success has been hit super hard with bots even long before LLMs came out in force but if you&#8217;ve ever interacted with an LLM in chat (or even worse voice) the lack of humanity comes out quickly and it is <em>degrading</em> in a moment when you need actual help!  I think companies should tread carefully in this space and there is a lot of value in having an actual real human who can help. Weirdly enough this is actually a differentiating feature now.</p><h1>AI In Organizations</h1><p>Where I think LLMs shine in an organization is not in replacing anyone but in simplifying the complexity of communication and helping reduce cognitive load for people enabling them to think and work on things that matter the most.</p><p>LLMs that summarize project statuses and can provide realtime stats help project managers focus on the inter personal and strategic decisions they actually <em>want</em> to be focusing on.</p><p>LLMs that integrate into slack that can create overviews of long text chains helps welcome new people into a complicated conversation and lowers the barrier and improves communication equity. We&#8217;ve all seen a 100+ thread and nope&#8217;d out because aint nobody got time for that.  </p><p>LLMs that can act as a souped up first pass triage for inter team questions like a Zapier on steroids is great for lowering team&#8217;s necessity to spend hand holding first touch interactions between teams.</p><p>There&#8217;s endless uses for NLP to remove annoyances.  However I firmly believe that these flows don&#8217;t replace anyone. If anything they should free people up to think and act and decide on more important things.  </p><p>As an example, I pasted a draft of this article into Claude and I tried to give it a non biased request for value</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6y3T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6y3T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 424w, https://substackcdn.com/image/fetch/$s_!6y3T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 848w, https://substackcdn.com/image/fetch/$s_!6y3T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 1272w, https://substackcdn.com/image/fetch/$s_!6y3T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6y3T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png" width="1456" height="453" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:453,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:80790,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.onoffswitch.net/i/171699726?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6y3T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 424w, https://substackcdn.com/image/fetch/$s_!6y3T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 848w, https://substackcdn.com/image/fetch/$s_!6y3T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 1272w, https://substackcdn.com/image/fetch/$s_!6y3T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf396905-fe56-4bf8-bf1c-74371fbeb8d7_1710x532.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And it certainly stroked my ego</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BLTN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BLTN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 424w, https://substackcdn.com/image/fetch/$s_!BLTN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 848w, https://substackcdn.com/image/fetch/$s_!BLTN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 1272w, https://substackcdn.com/image/fetch/$s_!BLTN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BLTN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png" width="1456" height="1504" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1504,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:906149,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.onoffswitch.net/i/171699726?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BLTN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 424w, https://substackcdn.com/image/fetch/$s_!BLTN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 848w, https://substackcdn.com/image/fetch/$s_!BLTN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 1272w, https://substackcdn.com/image/fetch/$s_!BLTN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07491c41-18bd-4841-b8f2-b2806a90cc77_1878x1940.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>But this is a great way of getting information quickly and if I want to dig in more I can. </p><h2>The 10^n fallacy</h2><p>We&#8217;ve all heard the &#8220;everyone is gonna be a 10000x engineer&#8221; but in reality I think LLMs make a 10x engineer a 12x engineer by freeing up their time to focus on things that matter.</p><p>I think LLMs make a 1x engineer a 0.5x engineer because they aren&#8217;t <em>getting better and learning</em> when relying heavily on LLMs.   Junior and mid engineers become seniors and staff and principals.  And unlike LLMs, humans do learn and get better.   Time invested in a person pays off over time.  Re prompting an LLM doesn&#8217;t.</p><p>Even in my example above, if I consistently and only use LLMs to summarize, or write for me, then I&#8217;m going to atrophy that muscle.  I often mentor my engineers to practice writing and reading comprehension.  </p><p>There&#8217;s been a lot of toxic hype about executives frothing to get rid of all their employees in favor of massive agentic webs, but every organization is only as good as it&#8217;s people.  This has been true for all time, pre-industrial revolution, post, the computing age, etc.  You need to invest in people.  </p><p>I think organizations are coming around. It was refreshing to hear at least one <a href="https://www.businessinsider.com/amazon-cloud-chief-replacing-junior-staff-ai-matt-garman-2025-8">executive</a> see through the <a href="https://www.livemint.com/companies/news/klarnas-ai-replaced-700-workers-now-the-fintech-ceo-wants-humans-back-after-40b-fall-11747573937564.html">hype veil</a>, but it seems that most executives are taking an extremely heavy micro-managing hand here, which just breeds resentment and skepticism about their actual incentives.  </p><p>Contrary to how <a href="https://techcrunch.com/2025/08/22/coinbase-ceo-explains-why-he-fired-engineers-who-didnt-try-ai-immediately/">Coinbase handled their AI policy</a>, I think this <a href="https://resources.github.com/enterprise/ai-powered-workforce-playbook/">GitHub</a> post is actually an interesting and healthy approach to AI centric tooling.  This snippet is key to me:</p><blockquote><p>Their mistake is treating AI adoption as a technology problem when it is, in fact, a change management problem. Companies fail at AI adoption because they treat it like installing software when it's actually rewiring how people work. The difference between success and failure isn't buying licenses. It's building the human infrastructure that turns skeptical employees into power users.</p></blockquote><p>Places that have created a major sense of distrust failed because it&#8217;s unusual for leadership to demand specific tool usage on employees!  I&#8217;ve never seen it before.  As an example, an IDE with semantic refactoring support saves an enormous amount of time and yet nobody has ever looked a vimlord in their dead eyes and told them to <code>:wq!</code> into IntelliJ.   I&#8217;m curious what incentives are at play that managers and executives who are many levels removed from day to work find it reasonable to dictate specific tooling? </p><p>The way to get people to use tools is to show them how it can make them more powerful and find more <em>joy</em> in their work.   And the goal isn&#8217;t to replace them, it&#8217;s to augment them. Sometimes that means answering questions that might be hard to google. Sometimes that means generating unit tests. Sometimes that means upgrading react native, and other times it means writing me a for loop because it&#8217;s boring. </p><p>That said I think removing engineers from coding is going to backfire.  Engineers are good because they know the systems they are in. If an mechanical engineer doesn&#8217;t know how torque works, can they design an engine? If an aerospace engineer isn&#8217;t living and breathing the thermo coefficients of materials can they actually build a re-entry hull for spacecraft?    Abstractions can remove someone from certain levels of work, few of us work in assembly for example. But we still need to know what is going on at all levels and sometimes we do need to drop in that low!</p><h1>Conclusion</h1><p>I think LLMs and AI are here to stay but the level at which people interact with them or find them helping in their life varies wildly.  For professionals who are using LLMs to speed up their work we need to be disciplined and diligent to not offshore our own brains to the agent.  We need to invest in people, like we always have, and let people discover what helps them and embrace those patterns.  We need to carve time out for people to improve themselves and deep understanding of things so that they can actual be useful in managing LLMs.  If we fall into the fallacy that everyone is working at 100x the speed how are we actually going to manage the 100x output? </p><p>We&#8217;re at an interesting point in time but in the end all you have is your brain so use it or lose it.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[What makes good tests?]]></title><description><![CDATA[Let&#8217;s add another &#8220;what makes good tests&#8221; article to the world!]]></description><link>https://www.onoffswitch.net/p/what-makes-good-tests</link><guid isPermaLink="false">https://www.onoffswitch.net/p/what-makes-good-tests</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Mon, 06 May 2024 16:00:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8p2q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let&#8217;s add another &#8220;what makes good tests&#8221; article to the world! Just kidding, but I do think this is a topic that is always worth discussing. People are well aware by now that writing tests is good because they not only validate that your code works but also validate that your contracts, assumptions, and dependencies work well together.</p><p>I talk about testing quite a bit in <a href="https://www.amazon.com/dp/B0D1QX58ZR">my book</a> but all that maters is that tests are readable and that they fail when you break your code.  </p><p>Here are a few guidelines to go by when thinking about your tests.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/subscribe?"><span>Subscribe now</span></a></p><h1>Single tests</h1><p>Tests should always run and fail in isolation.  You never want test setup to be dependent on other tests, or rely on any particular order of tests. Each tests should effectively work on a blank slate of the world. That either means its testing pure stateless code or if its interacting with the local word (via sqlite or docker or something) then the dependencies should be wiped between tests and set-up fresh for each test. Alternatively you can namespace and create new scoped spaces for long running dependencies - a new schema in sqlite, a new bucket in localstack s3, etc.  The underlying context stays up (db/docker) but the namespace/scoping changes per test.</p><h1>Setup should be clear</h1><p>Test setup should be clear so that you can tell what the test needs.  </p><p>Here&#8217;s an example of a <em>bad </em>test:</p><pre><code>it "validates context" do 
  expect(call).to eq(result)
end</code></pre><p>This is a bad test because </p><ul><li><p>It&#8217;s unclear what setup is happening (the setup happens outside the scope of the test)</p></li><li><p>It&#8217;s unclear where the result value is, or why it&#8217;s set up this way?  Again the scope is set up outside the test</p></li></ul><p>A better version of this would be</p><pre><code>it "validates context" do
  sample = 123

  insert_data(sample)

  expect(call).to eq(sample)
end</code></pre><p>Regardless of what may or may not be happening here you can clearly see that we set something up by inserting our data as part of the test, then validating that we got something back related to the insertion.  </p><p>The example here is silly but these kinds of tests happen all the time.  </p><p>Test setup in the real world can be very non-trivial, so ensure you wrap test setup in classes or utilities that make it easier to re-use.  These test setup utilities can and should be tested themselves too! If you need to set up a complicated API interaction wrap it up! If you need to generate sample data and insert it into in memory db wrap it up! The more you create your own internal testing API the more you can re-use this and then run faster, have stronger tests, and the tests themselves stay short <em>but clear</em> - the entire setup, call, validation is obvious.</p><p>I&#8217;ve talked about internal <a href="https://www.onoffswitch.net/p/what-makes-a-good-api">APIs before</a> and test setup tooling falls into that same bucket.</p><h1>Make sure it breaks</h1><p>Not a lot to say here other than make sure if you write tests make sure you break the code under test and validate that the test itself breaks! You might be surprised to find that sometimes your tests pass and even if you break your code&#8230; they still pass. That&#8217;s not a good test.</p><p>This is especially important when over-using mocks. I encourage teams I mentor to use as much real resources as possible. Actually talk to a database, actually make an in memory http call to your API, etc.  When you over-use mocks you might find that you are only testing what the mock says is doing, and that nowhere is the actual interaction between logical components is tested.</p><h1>Avoid cargo culting</h1><p>This brings to me another point to not fixate on cargo cult definitions.  It doesn&#8217;t matter if something is an integration test or a unit test, its just a test.  Some tests are small and stateless, other tests require more setup.  If the test provides value then its a valuable test!</p><p>This is technically an integration test because it talks to a database, but &#8230; who cares? </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8p2q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8p2q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 424w, https://substackcdn.com/image/fetch/$s_!8p2q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 848w, https://substackcdn.com/image/fetch/$s_!8p2q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 1272w, https://substackcdn.com/image/fetch/$s_!8p2q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8p2q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png" width="1456" height="493" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:493,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:194649,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8p2q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 424w, https://substackcdn.com/image/fetch/$s_!8p2q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 848w, https://substackcdn.com/image/fetch/$s_!8p2q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 1272w, https://substackcdn.com/image/fetch/$s_!8p2q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3de7f7fb-506d-4528-bbf0-36530ac5116d_1880x636.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice that it</p><ul><li><p>Sets is data up</p></li><li><p>Is isolated per test</p></li><li><p>Clearly asserts the contract under tests</p></li><li><p>Is obvious what the expectation</p></li></ul><p>This a recipe for a successful test.</p><h1>Conclusion</h1><p>Tests are first class code. Treat them as such.  Writing a good test is just like writing good non-tests - prefer readability, keep it simple, re-use and componetize shared dependencies, and don&#8217;t be afraid to refactor!</p><p></p>]]></content:encoded></item><item><title><![CDATA["Building A Startup" published!]]></title><description><![CDATA[Some of you may know but I&#8217;ve been working on a book putting all the best practices, anecdotes, code samples, and experiences I&#8217;ve had over the last handful of years into a book to help others grow and scale small companies in a way that is resilient and sustainable.]]></description><link>https://www.onoffswitch.net/p/building-a-startup-published</link><guid isPermaLink="false">https://www.onoffswitch.net/p/building-a-startup-published</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Mon, 15 Apr 2024 16:48:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!L4hP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Some of you may know but I&#8217;ve been working on a book putting all the best practices, anecdotes, code samples, and experiences I&#8217;ve had over the last handful of years into a book to help others grow and scale small companies in a way that is resilient and sustainable.  My book, <em><a href="https://www.amazon.com/dp/B0D1QX58ZR">Building A Startup - A Primer For The Individual Contributor</a>  </em>has hit the bookshelves and is available for purchase both as print and epub, as well as available here as a <a href="https://drive.google.com/file/d/1kMwBuI3Dzcf_IU2QU1kHpWu8HABf6bJJ/view">free PDF</a>.  </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L4hP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L4hP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 424w, https://substackcdn.com/image/fetch/$s_!L4hP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 848w, https://substackcdn.com/image/fetch/$s_!L4hP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 1272w, https://substackcdn.com/image/fetch/$s_!L4hP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L4hP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif" width="403" height="644.8" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e9c355dc-3d75-4790-bff6-2095d4abcfa3.tif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1000,&quot;width&quot;:625,&quot;resizeWidth&quot;:403,&quot;bytes&quot;:2579792,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/tiff&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!L4hP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 424w, https://substackcdn.com/image/fetch/$s_!L4hP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 848w, https://substackcdn.com/image/fetch/$s_!L4hP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 1272w, https://substackcdn.com/image/fetch/$s_!L4hP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c355dc-3d75-4790-bff6-2095d4abcfa3.tif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I&#8217;ve long talked about writing a book, I mean it&#8217;s basically just like 100 blog posts chained together sort of, but I didn&#8217;t get the chance to really sit down and write it till last summer when I was between jobs.  I decided to finally do a brain dump of all the things I&#8217;ve been evangelizing and putting into practice for years with the intent to be able to share this with new teams I join and people I mentor.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>For those interested in the book writing process it was surprisingly accessible.  I wrote my original manuscript in google docs, about 100 pages worth, and then hired a wonderful development editor off of <a href="https://reedsy.com/">reedsy</a> who helped me flesh the book out into something cohesive.  Then I hired a proof reader (again off reedsy) who polished the book and made it ready for publication.  At that point I handed the book off to a great graphic designer, <a href="https://www.christianarnder.com/">Christian Arnder</a>,  who beautifully designed the cover and inside layout of the book.</p><p>For e-pub we used the <a href="https://kdp.amazon.com/en_US/help/topic/GUGQ4WDZ92F733GC#download">kindle create</a> tool to upload a docx file and then do some tweaking and used the <a href="https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&amp;node=21381691011">kindle previewer</a> to make sure it formatted well and then it was easy to publish and release via amazon KDP.</p><p>I&#8217;d love for anyone who reads the book to drop a comment or review, and I&#8217;m always happy to hear feedback if there&#8217;s any questions or comments. </p><p>Enjoy!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[8 months of Ruby]]></title><description><![CDATA[Following in the vein of my last language retrospective, 8 months of Go, I wanted to share my experiences of working in Ruby for the last 8 months.]]></description><link>https://www.onoffswitch.net/p/8-months-of-ruby</link><guid isPermaLink="false">https://www.onoffswitch.net/p/8-months-of-ruby</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Mon, 15 Apr 2024 16:01:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/90c382de-487b-4981-979b-331130f391df_782x674.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Following in the vein of my last language retrospective, <a href="https://www.onoffswitch.net/p/8-months">8 months of Go</a>, I wanted to share my experiences of working in Ruby for the last 8 months.  Disclaimer: Ruby has never been my first choice of language, but like Go, I&#8217;ve gone into this with an open mind. </p><p>I&#8217;ve played with Ruby on and off for 20 years (I wrote a rudimentary early version of instagram back in 2003 in Ruby for friends and I really regret not pursuing that more ha).   Most often though I use Ruby for CI scripts because it&#8217;s installed on machines by default and is better than shell scripting when it comes to slightly non trivial tooling.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>For many years I shied away from taking jobs that used Ruby because&#8230; well &#8230; I like types! Why work in a language without them?  My entire blog is basically a paean of types.  However, I don&#8217;t like being afraid of something and I made a very conscious choice to work in an untyped language, be it Python or Ruby, for my next main job.</p><p>I&#8217;ll be honest, I don&#8217;t hate it as much as I thought I would.  There are some things I really like about it actually and just much as that I don&#8217;t like - some expected and some new and unexpected!</p><p>Let&#8217;s start with the things I like.</p><h1>Pros</h1><h3>terse and expressive</h3><p>This is Ruby&#8217;s biggest sell - the fact that the language is terse, concise, and extremely expressive. In the hands of a power user it&#8217;s actually fun.  I love the simple module and class syntax, writing abstractions feels almost like when I&#8217;m sketching code out&#8230; but it&#8217;s runnable! This reminds me a lot of F#.  I wrote an F# example once that read like like a sentence and a co-worker of mine was amazed that it wasn&#8217;t just pseudocode, it was actual code that did what the sentence said.  </p><p>I recently had to write some AST processing to transform a higher level DSL into an elasticsearch query and Ruby made it extremely easy to build a nice little API to model boolean and query logic. It felt joyful!</p><pre><code>def build(schema = nil)
  case @tree
  when And
    operator = @tree.not ? :must_not : :must

    {
      bool: {
        operator =&gt; @tree.terms.map do |term|
          QueryBuilder.new(term).build(schema)
        end,
      },
    }
  when Or
    {
      bool: {
        should: @tree.terms.map do |term|
          QueryBuilder.new(term).build(schema)
        end,
      },
    }
  ...</code></pre><h3>transforming data is easy</h3><p>Ruby comes with a lot of modern collection tooling. Dealing with maps, arrays, lazy iterators, it&#8217;s all built in and easy to use.  I love that you can make any class enumerable just by <a href="https://blog.appsignal.com/2018/05/29/ruby-magic-enumerable-and-enumerator.html">implementing the </a><code>each</code><a href="https://blog.appsignal.com/2018/05/29/ruby-magic-enumerable-and-enumerator.html"> function</a>.  Most of my day to day involves mapping, filtering, and slicing of collections - simple but obvious tasks that not every language makes easy to do.  It&#8217;s quite nice to just have it available, and I remember why Ruby made a big splash in the tech world in the early 2000&#8217;s and 2010&#8217;s. It was a language that embraced modernity and gave you the boilerplate free tooling to do what you need to do without a lot of overhead.</p><h3>meta programming is wildly powerful</h3><p>Message passing is a concept that a lot of people struggle with, but I find it makes more sense when you imagine it as an actor model of delegation.  You can call class <code>A</code> with method &#8220;<code>foo</code>&#8221; and if it decides &#8220;I don&#8217;t want to handle this&#8221; it can capture that and re-invoke something else instead for you.  Neat. But why would you want to do this? </p><p>Well, imagine you want to proxy all methods that start with <code>elastic_</code> to the Elastic processor and all methods that start with <code>rds_</code> to the rds handler but have it be composed and auto updated anytime anyone adds new methods? This is really easy now</p><pre><code>class Proxy
  def initialize(elastic, rds)
   @elastic = elastic
   @rds = rds
  end

  def method_missing(method_name, *args)
      case method_name
      when /elastic_.*/
         @elastic.send(method_name, args)
      when /rds_.*/
         @rds.send(method_name, args)
    end
 end
end</code></pre><p>Wild!  You get basically a method invocation for any missing method and can do whatever you want there.  </p><p>In the right hands this is crazy powerful and allows for very expressive and interesting code.</p><h3>zeitwerk and rails </h3><p>I really like in the zeitwerk and rails model of not having to include imports.  The forced naming convention of module namespaces map to directories and classes map to file names (all snake case) makes it really easy to find where things should be.  Granted this isn&#8217;t a problem in literally any other language because IDE&#8217;s manage all the imports and lots of languages require convention based file locations, but its still nice in Ruby too.</p><p>Rails also has a lot of great extensions and utilities that I sort of don&#8217;t even think about. I honestly don&#8217;t know sometimes if methods I&#8217;m calling are because of rails monkey patching or not, and to a certain extent I&#8217;m not sure I care. Rails mostly just works and stays out of the way and that&#8217;s fine with me.</p><h3>pry and the REPL</h3><p>Normally I prefer to do all my debugging in my IDE using attached breakpoints. I&#8217;m a sucker for gutter icons and right clicking on a test to step into it. But my work has moved to a model where we run all code in k8s remotely. This is kind of cool cause they can set up a bunch of pre-baked infra, and ensure consistent runtime environments.  However, remote debugging is often complex here and in other languages this might be more frustrating.  In Ruby though we can just debug&#8230; via Ruby! You can drop into an effective debugger via the pry shell by putting a <code>binding.pry </code>statement where you want the debugger to pop up. </p><p>You can customize actions and script things as well, because it&#8217;s all just Ruby.  The debugger is fully fledged, you can inspect locals, step into classes, add more breakpoints, etc.  I&#8217;ve found it to be a pleasant, albeit different, experience to what I&#8217;m used to.</p><h3>library ecosystem</h3><p>Ruby is like <a href="https://en.wikipedia.org/wiki/Stefon">stefon</a> - it&#8217;s got everything.  There seems to be a Gem that does just about anything you want, which is kind of nice. It reminds me a bit of the Node ecosystem where there&#8217;s a zillion packages to do a zillion things.  Ruby&#8217;s seems a bit more slimmed down and curated, and the packages themselves seem to be higher quality than a majority of Node packages.  I haven&#8217;t wanted for packages that do what I need in Ruby and it&#8217;s been easy and painless to consume them.</p><h3>sorbet</h3><p>I couldn&#8217;t help myself, I needed types anyways. Thankfully <a href="https://sorbet.org/">sorbet</a>  (from Stripe) adds a meta layer of runtime and static checking to ruby projects.  I remember when they were working on this back when I was at Stripe and I&#8217;m glad that it&#8217;s been open sourced and is pretty widely used.  Chime, Spotify, Stripe, and many others use Sorbet in production.  </p><p>Sorbet is a pretty weak guarantee, and all things the same a bad type engine, but for a language with <em>zero</em> types&#8230; sorbet is a life saver.  Teams that embrace sorbet gain a lot of benefit if anything not having to do manual runtime checks of input arguments is a big time saver anyways.</p><h3>rubocop</h3><p>I love linters. I used ESLint aggressive in the past and Rubocop is reasonably robust for what it does.  It has quite a few false positives though, but overall it&#8217;s better than nothing. Also being able to format code with the linter settings is a huge win, as I hate having formatting arguments. Just automate it and be done.</p><h3>delegation for composition</h3><p>This is just such a cool language feature, to be able to compose objects together and automatically proxy methods from one to another via </p><pre><code>delegate :&lt;the method to expose&gt;, to: &lt;some other object&gt;</code></pre><p>Which replaces writing</p><pre><code>def proxy_method
   @some_object.proxy_method
end</code></pre><p>It&#8217;s a small touch but this kind of composition is really effective as you can start with a proxy and then later implement the method yourself and it&#8217;s drop in compatible.   Creating composition layering like this is super easy and avoids a lot of the noisy boilerplate that other languages might have.</p><h3>rspec </h3><p>RSpec is a test runner tool for Ruby and it reminds me a lot of Jest. For the most part it&#8217;s just easy and stays out of the way which is why I like it.  I&#8217;ve had to write custom matchers a few times and it was trivial to extend RSpec (much simpler than Jest was) and overall does what I want with aplomb.</p><h1>Cons</h1><p>Fair warning, there is a lot I don&#8217;t like. &#175;\_(&#12484;)_/&#175; </p><h3>no types</h3><p>I knew this one going in, so I can&#8217;t really blame Ruby. But types are just so important to software it still hurts my brain to think that actual languages don&#8217;t have it.  I get why Ruby happened, if you time travel to the last 90&#8217;s the prevailing languages were Java and C/C++.  Talk about verbose monsters! Getting even a basic &#8220;hello world&#8221; required tons of files, compilation, etc.   Then forget about modern utilities like map or filter.  Hell Java didn&#8217;t have anything like that till their streams release with Java 8 till 2014! </p><p>But in the modern world this is a huge travesty to be missing types. Types are a part of the meta information about the problem domain you are solving.  They are information, and to wantonly discard it is short sighted.  I could probably write a whole novel about why types are good, but I will just say that missing types (and bolting it on with Sorbet) makes for a very painful experience in the real world, especially with larger projects, multiple developers, and actual real money at stake.</p><h3>hash obsessed</h3><p>Back to the lack of types, Ruby loves its untyped hash bag.  So much so that there are tons of collection utilities to deal with it. I worked in Node for years and the amount of times I used an untyped bag of <code>object</code> I could count on one hand. It really just shouldn&#8217;t need to be done.  Granted Ruby has structs which allow you to create immutable paired data but a lot of people just don&#8217;t use them.    But there are multiple flavors of structs - sorbet, dry-struct, ruby Data object, and they all &#8230; are slightly different!</p><p>This leads to people passing around untyped mutable object bags everywhere that has god knows what in it, sometimes adding fields (or even worse removing fields!) through a call chain. </p><p>Reasoning about systems that overuse hashes is an exercise in futility. I&#8217;m just too stupid to be a compiler, and so you have to step through code via unit tests which eats up endless amounts of limited time. </p><p>Hashes are fine when they are used as an informal object pairing inside of a function, or closed inside of a class (maybe) but exposing them as contract results of an API bleeds more and more loose shenanigans through the system which begets brittle systems that are hard to maintain.</p><h3>fractured type system (rbs vs sorbet/rbi)</h3><p>But there is hope!  In Ruby 3 they introduced type annotations via the RBS syntax, similar to .h files in the C world.  While this apparently isn&#8217;t meant to be user-generated (it&#8217;s supposed to be machine used) it&#8217;s the start of the ruby world moving to a place where types can at least be opt-in.</p><p>However, this now creates a fractured world where a lot of the big players are using sorbet but the language is moving to rbs.  This isn&#8217;t great for unification of things, and in fact I find that this fracturing is endemic in the ruby world. There&#8217;s 500 versions of things and everyone does stuff their own way.  </p><h3>dynamic exploration</h3><p>Due to the dynamic nature of Ruby (see some of the things I listed in pros) the language service is not that great. Discovery of what methods or fields are available via just hitting the <code>.&lt;tab&gt;</code> in your IDE is often non-existent. Granted you sort of learn the fields and methods you use on the regular, but self discovery doesn&#8217;t exist. You instead have to do a lot of reading of source code, poking around in a REPL, and hoping that the human managed documentation is correct (often it&#8217;s not).</p><p>Since there&#8217;s no compiler and there&#8217;s no way to validate that anything you write is correct or not, the only way forward it to <em>demand</em> 100% code coverage. Any line not covered is a line that could blow up. This is a very stark contrast to type checked languages where enforcing 100% coverage is a fools errand - you end up writing tests that are mostly garbage just to satisfy the requirement&#8230; oh wait, that happens in Ruby too!  In fact, because you can literally mock out the <code>.new</code> method on a class object people tend to skirt inversion of control and often mock (needlessly) just to get the coverage to pass.</p><p>Unfortunately this creates a false sense of security. Either you trust your tests or you don&#8217;t, and if you don&#8217;t then you end up afraid to make changes. Teams that are afraid to make changes won&#8217;t be incentivized to make improvements and the whole thing is self fulfilling.</p><h3>impossible to require contract adherence</h3><p>A lot of my complaints about Ruby are about contracts because contracts, and enforcement of contracts, is how you can support large scale teams and projects with grace.  Without being able to explicitly say &#8220;you cannot call me without filling this stuff in&#8221; you end up in a world where people have to fail to explore (either tests failing or production failing).  I would much rather lead people to water by not allowing their code to be valid until the contracts are satisfied (compilers are great!).  Just because something looks like a duck and quacks like a duck doesn&#8217;t make it a duck.</p><p>Not being able to adhere to contracts also cripples semantic refactoring via tooling like in an IDE. If the IDE can&#8217;t figure out that xyz uses abc then it can&#8217;t refactor things for you.  This means that refactoring now all has to happen by hand, which de-incentivizes the activity.  That then means that doing the organic cleanup that has to happen in a codebase often goes without, and as with all organic things code rots. If you  make doing the right thing hard and time consuming, people won&#8217;t do it. The easy thing has to be the right thing.</p><h3>sorbet (generics, tapioca)</h3><p>I keep coming back to this because I want to believe so badly that sorbet is going to provide the guard rails and safety that I want but I have to accept that it&#8217;s a very poor mechanism and frankly not that robust.  Unlike typescript which allowed libraries to bundle type definitions out of band sorbet has no way to bundle that data with a library.  You end up having to use another tool from Shopify called <a href="https://github.com/Shopify/tapioca">tapioca</a> to re-compile and scan the type annotations from all your Gems. </p><p>This means anytime you update a gem, you have to manually run some tooling.  It also forces your repo to house all the generated type information which frankly should be hidden away in a Gem and not versioned in my repository. </p><p>On top of that sorbet has no way to validate any kind of generic or higher kinded type, so even trying to do basic signatures like </p><pre><code>sig { params(data: T::Array[String]).void }</code></pre><p>is mostly useless because all sorbet can guarantee to you is that <code>data</code> is an array, but not an array of <code>String</code>.</p><p>Shapes is a nice thing of sorbet, allowing you to structurally type object bags, but it doesn&#8217;t allow you to model missing fields and there is no concept of type narrowing (like with typescript). The end result is a lackluster experience, especially having come from the insane powers of typescript.</p><p>At this point I have resigned that sorbet is yardoc++ with some runtime checks. I still use it, because a large part of the time it is able to validate types but it really misses the mark on being actually fully fledged.</p><h3>rails conventions outside of MVC</h3><p>I&#8217;m on board with auto loading, and directory conventions with rails. What I find to be particularly annoying though is that top level folders of rails projects are automatically in the root namespace.  What this means is you can have logical data of a module split across mulitple disparate locations. For example, this class</p><pre><code>module Foo
 class Bar
 end
end</code></pre><p>can live anywhere in the following format <code>/&lt;root folder&gt;/foo/bar.rb</code>.</p><p>Why is this an issue? Well imagine you want to co-locate a module&#8217;s read/write/entities/logic/etc. The rails folder structure with model, controller, etc is very much tied to an old school MVC idea. </p><p>I find this makes it hard to properly organize your code because not everything slots in so neatly in the real world.  Code is less organized by function and more by domain. I want to co-locate top level entrypoints like API handlers, but otherwise have things delegated to domain function. For example:</p><pre><code>/core
  /foo
   .. all the things foo needs    
  /bar
   .. all the things bar needs
/api
  .. calls things in core
/db
  .. shared db things
/common
  .. stuff everyone uses
</code></pre><p>I know you can get this with rails but the fact that it forces a convention that doesn&#8217;t encourage your own folder modeling is frustrating.  So you end up with code in folders that doesn&#8217;t quite make sense, and its split across unclear lines.  Maybe this is a reflection of my personal experiences, but I haven&#8217;t ever dealt with this level of disorganization in other languages.  </p><h3>symbols vs strings inconsistencies</h3><p>Ruby in the old days didn&#8217;t inter strings, so every instance of a hardcoded string was a new allocation.  Instead they had the concept of symbols, the funky stuff with a leading colon: <code>:this_is_a_symbol</code> and is meant to  basically act as a single instance of that &#8220;stringlike&#8221; object.  Every where you see that same symbol it&#8217;s the same memory representation.  That&#8217;s fine, a nice clear distinction! Use symbols as object keys/etc and strings to be strings.</p><p>Well later Ruby decided that this was dumb, and now strings are automatically frozen so that this distinction no longer matters. But what we are left with is inconsistent usages of when things are symbols and when things are strings.  You now often have hashes that intermix the two</p><pre><code>{
  :symbol =&gt; {
    "string" =&gt; {}
  },
  "string2" =&gt; {}
}</code></pre><p>Which makes your object pattern access wildly inconsistent.  To work around this people often <code>deep_stringify_keys</code> or <code>deep_symbolize_keys</code> and then sprinkle on <code>.to_s</code> or <code>.to_sym</code>  to access keys and get results.  This just adds more feed for the fodder about ruby being slow since we&#8217;re iterating on objects and converting things to work around shape nuisances.</p><p>I&#8217;ve seen so many failed instances in production due to string/symbol inconsistencies its amazing this is something a language allows you to do. </p><p>I&#8217;m sure that the argument is &#8220;<em>if you know what you are doing you you can create nice API&#8217;s and be consistent</em>&#8221; but in the real world most people don&#8217;t know what they are doing and need guardrails to make sure they do the right thing. </p><h3>slow</h3><p>Ruby is just slow.  Time and time again I&#8217;m shocked at how slow it is when you start to throw real data at it. If you have to process a few thousand items but loop over them a couple of times (really trivial honestly) the time can start to creep up. </p><p>I recently had a situation where we had to process 10k items, which included mapping them once, running each item item through a string template, and then serializing it all to JSON. This process took over 20 seconds. After doing benchmarking I found that all the time was just spent in ruby iteration time, so there was very little I could do to speed this process up.  </p><p>Usually I don&#8217;t care that things are slow, but this was&#8230; hilariously slow. Doing the same thing in node, or go, or java, would have taken milliseconds. </p><h3>procs and lambda gotchas</h3><p>I don&#8217;t know if I&#8217;m missing something or if this is due to a side effect of implementation, but requiring to <code>.call</code> a lambda or a proc really makes it annoying to pass around.  This forces a distinction between a function, method, or proc/lambda and that nuance isn&#8217;t necessary in any other language or ecosystem.  I&#8217;m not sure what the reasoning there is.</p><p>On top of that the various return semantics of procs vs lambdas is convoluted where in one model it returns from the lambda itself (like every other language in the world) and in another it forces a return call of the pattern&#8230; which is weird. This variance continues to illustrate the lack of consistency in the Ruby world where there&#8217;s a nuanced flavor of everything but no coherence in language design. It&#8217;s almost like they did it because they could.</p><h3>case when pattern matching&#8230; sort of</h3><p>The case statement is pretty cool allowing you to do some basic pattern matching in ruby as I illustrated earlier in the method missing example. But deep down inside this works by abusing a triple equals operator, which makes the case statement both powerful (in that you can customize this behavior) and insane (in that it&#8217;s not discoverable at all that this is what drives the behavior).  Often I reach for a case statement but find that the triple equals of the thing I&#8217;m comparing doesn&#8217;t work and I&#8217;m forced to do an ugly if  tree. </p><h1>Conclusion</h1><p>Given the choice would I choose Ruby to build anything on my own? Definitely not.  But would I accept a job where they work in Ruby? If the bar was high, the team philosophies matched mine, and the product (and compensation) was stellar I would.  But all things being equal I think I&#8217;ll stick to typed languages - kotlin, typescript, java, go, c#, etc.</p><p>Ruby  was an extremely attractive, modern, option in 2015 but now feels like a poorly curated frankenstien and I keep bumping up into these rough edges which takes the joy out of the things that are joyful in Ruby. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Typed pubsub ]]></title><description><![CDATA[Dealing with circular dependencies]]></description><link>https://www.onoffswitch.net/p/typed-pubsub</link><guid isPermaLink="false">https://www.onoffswitch.net/p/typed-pubsub</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Mon, 18 Mar 2024 16:01:04 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fb715d39-ba2b-43b4-83b2-fe471dd9853d_854x530.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Not too long ago I was working on a project where even with careful dependency management a circular dependency arose. A needed B which needed A. Of course its never so clear the distinction, its usually through several other layers of necessities. A needs B needs C needs D which needs A.  Oof.  This was a major frustration because untangling this dependency chain, in my scenario, was non trivial, and more importantly would break some very logical segmentation and API contracts if I did.  </p><p>So what was I supposed to do?  There are a few ways to handle this, one way is to use a lazy <a href="https://github.com/paradoxical-io/ts/tree/630c91ffe17ebdf3f405c9a4956056c485b19ebb/packages/common/src/di">dependency initializer</a> and another is to use a pub sub model. In this post I&#8217;ll talk about a <a href="https://github.com/paradoxical-io/ts/tree/630c91ffe17ebdf3f405c9a4956056c485b19ebb/packages/common/src/pubsub">strongly typed in memory pub sub</a>.</p><h1>Pub sub</h1><p>Pub(lish) and sub(scribe) is a pattern to decouple the necessity of interacting with a components directly.  Instead of A calling B directly let B subscribe to events that it needs to react on. This can be really nice when you have lifecycle type things - account created, user deleted, stuff like that.  When a user is deleted, you might need to delete recurring jobs, delete accounts, notify someone, etc.  A direct call pattern might look like</p><pre><code>class Users {
  ...

  async userDeleted(id: UserId): Promise&lt;void&gt; {
    await Promise.all([
      accounts.delete(id),
      jobs.unregister(id),
      notifications.notifyUserDeleted(id)
    ])
  }
}</code></pre><p>But this has problems in that if <code>accounts</code>, <code>jobs</code>, or <code>notifications</code> needs <code>users</code> then you have a circular problem.</p><p>A simple solution is to use an in memory (or swappable) pubsub abstraction</p><pre><code>class Users {
  ...

  async userDeleted(id: UserId): Promise&lt;void&gt; {
    await pubsub.notify({ type: 'user-deleted', id: id })
  }
}</code></pre><p>And let anyone subscribe to this event.  Building a simple version this is trivial, register hooks based on type and invoke based on registration.  Then processing events is as easy as </p><pre><code>pubsub.register('user-deleted', async event =&gt; { ... })</code></pre><p>Pubsub happens all the time, its just another form of event processing. In fact using a pattern like this can be quite nice because you can even swap out the internals and allow for over the wire queueing as well.  When you have a generic PubSub&lt;T&gt; abstraction creating decoupled interactions is quite nice.</p><p>Building on this simple idea what I really want is to</p><ul><li><p>Have the compiler validate that registered type callbacks are actually correct, and ensure that the handler of the event is typed</p></li><li><p>Ensure that we can only notify the right types of events and that event types are properly typed</p></li></ul><p>Lets do some typescript magic. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/subscribe?"><span>Subscribe now</span></a></p><h1>Type magic</h1><p>If we have some sample events </p><pre><code>interface A {
  type: 'a';
  data: number;
}

interface B {
  type: 'b';
  other: number;
}

type E = A | B;</code></pre><p>Then to get the set of all types &#8220;a&#8221; and &#8220;b&#8221; we can create a conditional type </p><pre><code>type Types&lt;Obj&gt; = Obj extends { type: infer Key } ? (Key extends string ? Key : never) : never;</code></pre><p>Where if we do <code>Types&lt;E&gt;</code> should return to use the set of &#8220;a&#8221; and &#8220;b&#8221;</p><p>We can even make a reverse lookup, so if we know <code>E</code> and the type of &#8220;a&#8221; we can resolve the type of <code>A</code></p><pre><code>type ByType&lt;Obj, Key extends string&gt; = Key extends Types&lt;Obj&gt; ? (Obj extends { type: Key } ? Obj : never) : never;</code></pre><p>We can now make a class with a signature like this</p><pre><code>export class PubSub&lt;T extends { type: Types&lt;T&gt; }, Keys extends string = Types&lt;T&gt;&gt; </code></pre><p>Which requires a shape of </p><pre><code>{
   type: "...",
   ...
}</code></pre><p>For all events, and gives us a generic called <code>Keys</code> with the values of &#8220;a&#8221; and &#8220;b&#8221;</p><h1>Subscribing</h1><p>We can build a subscribe method really easily now that allows consumer to register a callback based on the event type</p><pre><code>protected callbacks = new Map&lt;Keys, Array&lt;(data: T) =&gt; void | Promise&lt;void&gt;&gt;&gt;();

/**
 * Subscribe to an event
 * @param key the type of event to subscribe to
 * @param onEvent  A callback with that type of event
 */
subscribe&lt;Key extends Types&lt;T&gt;&gt;(key: Key, onEvent: (data: ByType&lt;T, Key&gt;) =&gt; void) {
  if (!this.callbacks.has(key)) {
    this.callbacks.set(key, []);
  }
  if (this.callbacks.has(key)) {
    this.callbacks.get(key)!.push(onEvent as (data: T) =&gt; void);
  }
</code></pre><p>If we look at an example usage</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gOF8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gOF8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 424w, https://substackcdn.com/image/fetch/$s_!gOF8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 848w, https://substackcdn.com/image/fetch/$s_!gOF8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 1272w, https://substackcdn.com/image/fetch/$s_!gOF8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gOF8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png" width="942" height="252" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/79965b3e-4007-4981-8ec8-8135423128dc_942x252.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:252,&quot;width&quot;:942,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46458,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gOF8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 424w, https://substackcdn.com/image/fetch/$s_!gOF8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 848w, https://substackcdn.com/image/fetch/$s_!gOF8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 1272w, https://substackcdn.com/image/fetch/$s_!gOF8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79965b3e-4007-4981-8ec8-8135423128dc_942x252.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We can see that by providing the key of &#8220;a&#8221; the callback is automatically typed to the type of A. Very cool.</p><h1>Publishing</h1><p>How about publishing?</p><pre><code>/**
   * Waits on all consumers to finish.
   * @param event
   * @param onError if set, proxies errors to this and nothing is thrown
   */
  async publish(event: T, onError?: (e: unknown) =&gt; void): Promise&lt;void&gt; {
    await Promise.all(
      this.callbacks.get(event.type as Keys)?.map(async callback =&gt; {
        try {
          const result = callback(event);

          if (result instanceof Promise) {
            if (onError) {
              await result.catch(onError);
            } else {
              await result;
            }
          }
        } catch (e) {
          if (onError) {
            onError(e);
          } else {
            throw e;
          }
        }
      }) ?? []
    );
  }</code></pre><p>Publishing all we have to do is given the type of the message, look up the callbacks for that type, invoke their handlers, do some exception handling, and await the result.</p><p>I don&#8217;t have unsubscribe but it can very easily be added by having subscribe return a function that allows you to unsubscribe which would remove that listener from the callbacks set.</p><p>Now we have a fully strongly typed pubsub that type checks the publishers and subscribers!</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BXVV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BXVV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 424w, https://substackcdn.com/image/fetch/$s_!BXVV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 848w, https://substackcdn.com/image/fetch/$s_!BXVV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 1272w, https://substackcdn.com/image/fetch/$s_!BXVV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BXVV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png" width="1456" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:125335,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BXVV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 424w, https://substackcdn.com/image/fetch/$s_!BXVV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 848w, https://substackcdn.com/image/fetch/$s_!BXVV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 1272w, https://substackcdn.com/image/fetch/$s_!BXVV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64ae0870-5e91-48ca-9961-0952d8682326_2074x270.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h1>Pitfalls</h1><p>Pub sub models are highly flexible and can really help in a lot of situations, however I would warn to not abuse this though as understanding the implications of who does what when can get extremely complicated. If you&#8217;ve ever built UI forms that are all async knowing who reacts on a &#8220;click&#8221; can be mind bogglingly complex.  </p><p>To make that simpler I would suggest that event registrations are all centralized, have one place in code where you register all your listeners so that you can quickly audit who is reacting to what and when.  For example, do this</p><pre><code>pubsub.subscribe('a', handler1)
pubsub.subscribe('a', handler2)
...
pubsub.subscribe('a', handler20)</code></pre><p>And avoid this temptation</p><pre><code>class Handler1
  constructor(pubsub: PubSub&lt;E&gt;)
   pubsub.register('a', this.handler) 
  }
}</code></pre><p>Because this side effect is not <em>clear</em> from the api contract.  You aren&#8217;t sure if you are publishing OR subscribing to the pubsub just by the constructor argument list.</p><p>In fact, I might even suggest that registration and publication are separated into different interfaces so you can create stronger contracts.  This would allow only certain areas to register and only certain areas to publish, which you could use to control access to the eventing pipeline.</p><p>As with all things, there&#8217;s a lot of options and considerations to be had.  </p><p>Full source available on my <a href="https://github.com/paradoxical-io/ts/tree/630c91ffe17ebdf3f405c9a4956056c485b19ebb/packages/common/src/pubsub">github</a>.</p><p></p>]]></content:encoded></item><item><title><![CDATA[What makes a good API?]]></title><description><![CDATA[There&#8217;s a ton of thoughts and written material about writing good public API&#8217;s for things like REST or RPC but I feel like the amount of times I am writing a public API is dwarfed by the number of internal contracts and API&#8217;s I build on the daily.]]></description><link>https://www.onoffswitch.net/p/what-makes-a-good-api</link><guid isPermaLink="false">https://www.onoffswitch.net/p/what-makes-a-good-api</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Mon, 04 Mar 2024 17:00:26 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fe9f00c4-788f-46d1-bad1-e3e16271ac76_1716x1146.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There&#8217;s a ton of thoughts and written material about writing good public API&#8217;s for things like REST or RPC but I feel like the amount of times I am writing a public API is dwarfed by the number of internal contracts and API&#8217;s I build on the daily.</p><p>I consider every layer of an application as a distinct set of API&#8217;s that layers talk to - database repositories, business logic classes, externally accessible API&#8217;s, unit tests, etc.  Each layer here has its own boundaries and those boundaries I care a lot about.  </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Creating strong, consistent, contracts, makes these different lego pieces composable. This is really how you build flexible systems! </p><p>Things I consider when crafting an API are</p><ul><li><p><strong>Do the naming conventions match other layers</strong>?  If we call something an <code>annotation</code> in on place, are we calling it an <code>attachment</code> in another? That inconsistency makes fitting pieces together annoying, and potentially error prone.<br></p></li><li><p><strong>What data we taking into our API</strong>? Are we exposing data objects that only take (and return) what we need? Even if what we need at this layer is a subset of what a another layer needs, we shouldn&#8217;t conflate the data object layers. For example, if you have a config class for a mapping layer like below, if the <code>v2 engine</code> takes a lot of the same properties we probably still want to create a new unique interface for the v2 engine instead of re-using this config.  Why? Well, what does <code>enableV2 </code>even mean once you are already in this <code>v2 engine</code>?  It&#8217;s kind of a nonsense thing, since it was presumably used for logic to determine if you even enter this engine or not.</p><pre><code><code>interface MappingConfig {
  // enables the v2 engine
  enableV2: bool
  
  // flags related to mapping
  useDistinctNames: bool
  ...
}

interface EngineConfig {
  // flags related to the engine
  useDistinctNames: bool
  ...
}</code></code></pre><p>This model can probably be simplified by composing the configs, but the point stands which is to isolate and control the data at each layer and not <em>leak</em> context<br></p></li><li><p><strong>Consistent access patterns.</strong>  This goes hand in hand with naming, but we want contracts and API&#8217;s within the system to play well together.  If one thing works on batches and another thing works on individual units, are they easily composed with mapping? Is it simple to transform and consume data between layers? If not, then the API is clunky and needs to be adjusted.  Internally to a codebase you have a lot of flexibility and I often argue that we should ruthlessly refactor our internals to make this easy for us.  After all, when things are smooth we can deliver value (product, bugfixes, features) faster! I talk about this at length in my upcoming book &#8220;<em>Building A Startup - A primer for the individual contributor</em>&#8221; (which should come out sometime in April).  <br></p></li><li><p><strong>Internal re-use? </strong>Are you able to re-use sections of your code often and plug in different strategies, implementations, and components to create new functionality? If not, why not? Most non trivial codebases have a ton of supporting tooling and classes - rate limiters, loggers, feature flag providers, reflection wrappers, etc.  When systems are properly componentized they allow you to plug and play building blocks. It should be obvious when this happens! A build block takes no more no less than it needs. If you find its missing something, really evaluate if that&#8217;s a generic thing that needs to be exposed or if the interactions between other objects is what needs tweaking.   </p><p></p><p></p></li></ul><h1>Conclusion</h1><p>Building internal tiered API&#8217;s is a little bit of a hand-wavy art. It&#8217;s impossible to give concrete examples because every system is different.  What I can suggest is that you listen to your gut and constantly evaluate if the lego blocks you are using feel like they are fitting right, minimizing repetition, and maximizing re-usablity. It is ok to duplicate things if they act as linear separations between layers.  Contracts and distinct lines drawn are important and they let you iterate safely at different locations in your codebase.  </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">onoffswitch.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Musings about "On Call"]]></title><description><![CDATA[In the cloud enabled world where everything runs 24/7 someone behind the scenes is usually on the hook to make sure that when things break off hours that a human reacts. On call is often a team based rotation of a few days to a week with varying degrees of guarantees. Some teams have SLA&#8217;s that require a reaction immediately, others can tolerate more graceful degradation. In any case at some point someone gets the dreaded page from PagerDuty.]]></description><link>https://www.onoffswitch.net/p/musings-about-on-call</link><guid isPermaLink="false">https://www.onoffswitch.net/p/musings-about-on-call</guid><pubDate>Mon, 19 Feb 2024 17:00:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/aa5849fc-15ff-4249-912c-34b77b6caff6_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the cloud enabled world where everything runs 24/7 someone behind the scenes is usually on the hook to make sure that when things break off hours that a human reacts.  On call is often a team based rotation of a few days to a week with varying degrees of guarantees. Some teams have SLA&#8217;s that require a reaction immediately, others can tolerate more graceful degradation. In any case at some point someone gets the dreaded page from PagerDuty.</p><p>I&#8217;ve been on call thousands of times in my career with varying degrees of annoyance and I got to thinking about how I view on call, what it is, what&#8217;s the responsibility, and how to improve it when it&#8217;s painful. </p><h2>What is it?</h2><p>The on call person is a first responder.  In my mind their only job is to be around off-hours to react when something horribly breaks.    Things that go down during business hours the entire team should react to.  Engineering is a team sport.  Off hours, if paged the on call person should be able to quickly check some dashboards, graphs, logs and determine if something is on fire and whether they need to escalate to others to help out or do some sort of obvious manual intervention to get the system stable.   </p><p>That&#8217;s basically it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/subscribe?"><span>Subscribe now</span></a></p><h2>What it isn&#8217;t</h2><p>Defining on call is easy, but defining what on call <em>isn&#8217;t</em> requires a bit more exposition. </p><p>First, the on call person isn&#8217;t a dumping ground of &#8220;keep the lights on&#8221; tasks, and they certainly aren&#8217;t responsible for <strong>fixing</strong> the broken thing.  Their only job is triage, provide an immediate response (if it&#8217;s obvious) and otherwise it&#8217;s just to say <code>ACK</code> and raise the alarm.  </p><p>Some teams like to have the on call person be the point of contact for team questions during the week and thats mostly fine, but those again should be not super invasive.  Like with code you can minimize interruptions by <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> - create FAQ&#8217;s, document your systems, invest time into self-serve solutions.  Being pestered all day is just as bad as being paged all day.  </p><p>In fact pages should be <em>rare</em>.  For about 4 years I was the primary on call at a small startup (by nature of being the founding engineer on a very small team) and I was paged only a handful of times the entire time.  This was an ideal on call scenario! It&#8217;s not that we ignored pages, its that we prioritized making sure that things <em>don&#8217;t page</em>. That means making systems resilient, and being actionable to every page to ensure it doesn&#8217;t happen again (unless things are critically on fire).</p><p>The on call person should mostly be able to live their lives without even realizing they are on call. It should not be impactful, and certainly people should feel empowered to go to the gym, shop, eat out, during their on call time.   Paired with a backup secondary, and a tertiary manager based escalation, the amount of times all 3 people are gone is very rare.  </p><p>I have been on teams where people are terrified to be away from a laptop for more than a few minutes and their lives grind to a halt for their on call time.  I think it&#8217;s annoying and a sign of poor team health when people are coordinating minute-to-minute &#8220;I will be <a href="https://dictionary.cambridge.org/us/dictionary/english/afk">AFK</a> from 2:15 to 2:32&#8221;.  This is not healthy, and is in fact extraordinarily toxic because it self selects for people without families, hobbies, caretakers, etc. It doesn&#8217;t have to be this way!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!It8C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!It8C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 424w, https://substackcdn.com/image/fetch/$s_!It8C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 848w, https://substackcdn.com/image/fetch/$s_!It8C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 1272w, https://substackcdn.com/image/fetch/$s_!It8C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!It8C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif" width="480" height="268" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:268,&quot;width&quot;:480,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1703846,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!It8C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 424w, https://substackcdn.com/image/fetch/$s_!It8C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 848w, https://substackcdn.com/image/fetch/$s_!It8C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 1272w, https://substackcdn.com/image/fetch/$s_!It8C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabf9b5be-0f3f-4fbe-bd96-82830bbcf856_480x268.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Making on call better</h2><p>What do you do if your on call is a dumpster fire?  There&#8217;s a handful of ways to improve this process and if you have executive and leadership buy in this is absolutely possible to improve.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>  </p><p>The rule for improving on call long term is to be <strong>actionable</strong> about a page. An ignored page is one that will come back.  There are four actions that should be considered every single time a page occurs</p><ul><li><p><strong>Tune</strong> - If the alert is too sensitive, tune it.  Should the alert page you at all? Is it actually important that someone at 2am gets up to look at this?  Should you adopt a multi tiered paging model of business hours vs off-hours paging? I discuss topic this in my book &#8220;<em>Building a Startup - A primer for the individual contributor</em>&#8221; in depth (due to be released this spring).</p></li><li><p><strong>Remove</strong> - Ask yourself, does this alert even need to exist.  If you just ack the alert and it goes away, or you mute it and move on, then delete it.  Be ruthless in your decisioning.  </p></li><li><p><strong>Fix</strong> - Regardless if the alert was real or not, is there a way to fix the underlying issue? If a queue is backing up and you are alerting on it, a fix can be to auto scale workers based on queue size.  If a 3rd party provider is failing with 429&#8217;s (rate limited) can you slow down your request rate programmatically?  What other ways can you do to make your system more resilient.   Can you detect errors ahead of time and react to them?  Can you repair broken data automatically? Can you use twilio to call the support of a bad banking processor partner<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>?</p></li><li><p><strong>Escalate</strong> - If the world is actually on fire the on call person is not going to fix this themselves and needs to put the bat-signal up.   This is the only acceptable time something should page someone in my mind.</p></li></ul><p>There is no world where a page does result in one of these actions.  Teams that don&#8217;t take action are doomed to have tough on call rotations, and that breeds resentment and burnout.  Teams that ignore pages or do an &#8220;ack and wait&#8221; model since things &#8220;tend to resolve themselves&#8221; are basically telling each other &#8220;we don&#8217;t value your time&#8221;.  </p><p>Alerts that actually do fire should include information about why they were set up in the first place. What are they monitoring, why are they monitoring it? </p><p>Runbooks<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> are common to include on alerts, but in my mind the runbook should not be &#8220;here is how you fix it&#8221;, instead the runbook should be &#8220;here is how you validate the severity of this and get more details&#8221;. If the runbook describes a step by step situation to solve a page can it be programmatically enforced?  Obviously some things like &#8220;<em>the MySQL instance needs to be restarted because this rare scenario has occurred</em>&#8221; is not worth programmatically controlling but this alert should also be extraordinarily rare!</p><p>Reacting to pages takes a lot of work, especially if they&#8217;ve been neglected for a long time.  But it is possible to make improvements, the team just needs to be militant about responding to each and every one.  Avoid the temptation to have dedicated time to improve things, that never works. When you fall into the trap of doing &#8220;let&#8217;s have a sprint where we just squash bugs&#8221; you&#8217;re telling the team that reacting continuously doesn&#8217;t matter and that it&#8217;s ok to ignore things when its not part of regular scheduled programming.</p><p>To me, off hours pages should be treated like a catastrophe. It&#8217;s all-hands-on-deck the next morning if a page happened and the entire world isn&#8217;t melting.  Every person on the team should be clamoring to make improvements, because it&#8217;s could very well happen on their watch next time!</p><p>If an incident does happen the team needs to do work to make sure that the cause of that incident can&#8217;t ever happen again.  I like to frame a lot of my architectural decisions in how much I hate getting paged.  I mull on every failure mode and whether the system is resilient enough to handle it.  In the beginning it feels a little like whack-a-mole, plugging holes as you find them. But over time, systems become hardened, almost bullet proof, if teams actually follow through on it.  </p><p>With practice this mindset becomes part of every system design, so you build resilient systems first instead of building reactive systems that need to become resilient later.</p><h2>Conclusion</h2><p>Investing time and energy into improving the on call process can massively impact the morale of a team.  Not to mention this is a huge selling point in recruitment - if you care about the on call process people will clamor to join your team, and you can encourage the best and brightest to do their best work with fewer distractions.  </p><p>A large part of the on call process is cultural - what does your team care about and how do they prioritize their work?  Teams that don&#8217;t iterate and action on the on call process tend to sink under their own weight and it becomes a self fulfilling cycle of failure. Pages get ignored, then real issues get missed, incidents take longer to resolve, leadership gets frustrated, the team feels more pressure, goto 1.</p><p>With focus and determination, the on call process can be easy and forgettable! </p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/p/musings-about-on-call?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thank you for reading Onoffswitch . This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/p/musings-about-on-call?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/p/musings-about-on-call?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>If you don&#8217;t have buy in, then leave.  You must have support to make improvements otherwise things cannot improve.   It&#8217;s sad to say but it&#8217;s well known that most people leave bosses, not jobs.  </p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>I actually did this.  They kept telling us to call their support anytime they had an outage, but why should I wake up at 2am to call them? Computers are cool as hell, use them!</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>A runbook is just a description of steps </p></div></div>]]></content:encoded></item><item><title><![CDATA[Ruby ERB templating and AST magic]]></title><description><![CDATA[Validating invalid dynamic erb blocks]]></description><link>https://www.onoffswitch.net/p/ruby-erb-templating-and-ast-magic</link><guid isPermaLink="false">https://www.onoffswitch.net/p/ruby-erb-templating-and-ast-magic</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Mon, 05 Feb 2024 17:00:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e09c11f0-bbdc-430f-8f3e-67ba06c30d6a_420x300.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ruby and me are new acquaintances.  I&#8217;ve used it on and off for random scripts and essentially as bash++ but haven&#8217;t really built anything <em>real</em> in it in almost 20 years.  I&#8217;d shied away due to the lack of static typing but since <a href="https://sorbet.org/">sorbet</a> it has gotten a lot more palatable to me.</p><p>Recently I was working on building a templating engine<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> that allows external stakeholders to create display based templates based on some predicate matching, i.e. if this template matches some values then apply it and use the override result. In this case a template is an ERB block to evaluate.  <a href="https://github.com/ruby/erb">ERB</a> is a templating syntax that comes out of the box in Ruby.  For example:</p><pre><code>when:
  some_field: &lt;matches some value&gt;
override:
  some_other_field: "This is an erb block &lt;%= value %&gt;.</code></pre><p>A simple example using ERB to render this would look like</p><pre><code>ERB.new("This is an erb block &lt;%= value %&gt;.").result_with_hash({
  value: 123
})</code></pre><p>Which generates </p><pre><code>This is an erb block 123.</code></pre><p>Cool. But what happens if <code>value</code> is missing? ERB is going to be totally fine with that and just render</p><pre><code>This is an erb block  .</code></pre><p>Notice the missing ending value.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8jOx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8jOx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 424w, https://substackcdn.com/image/fetch/$s_!8jOx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 848w, https://substackcdn.com/image/fetch/$s_!8jOx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 1272w, https://substackcdn.com/image/fetch/$s_!8jOx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8jOx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif" width="480" height="261" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:261,&quot;width&quot;:480,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1303913,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8jOx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 424w, https://substackcdn.com/image/fetch/$s_!8jOx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 848w, https://substackcdn.com/image/fetch/$s_!8jOx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 1272w, https://substackcdn.com/image/fetch/$s_!8jOx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bd02d6c-5819-4b41-9aa8-cc416dbb7872_480x261.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I wanted to ensure that we could <strong>prevent a user from ever allowing a template to process empty strings</strong> creating nonsensical ERB resultant data.</p><p>To  do that I opted to find a way to break down the ERB template string into its component parts, effectively an <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">AST</a> of the ERB syntax.  By getting access to an AST we could then render <em>each segment</em> and validate that its result is non empty.   AST&#8217;s are fun and it&#8217;s a great way to think about a problem when you decompose something into its tree based metadata.  AST&#8217;s let you lean into your favorite LISPism: &#8220;<a href="https://en.wikipedia.org/wiki/Code_as_data#:~:text=Specifically%2C%20%22code%20is%20data%22,a%20given%20compiler%20or%20interpreter.">code is data</a>&#8221;.</p><p>Thankfully I didn&#8217;t have to write an ERB parser, and was able to find one called <a href="https://github.com/judofyr/temple">Temple</a>.  Using Temple we can break down the  ERB AST now by simple doing:</p><pre><code>template = "This is an erb block &lt;%= value %&gt;."

erb_parser = Temple::ERB::Parser.new

erb_parser.call(template)</code></pre><p>Which will give us a loosely structured hash based syntax tree of the form</p><pre><code>[
  [0] :multi,
  [1] [
    [0] :static,
    [1] "This is an erb block "
  ],
  [2] [
    [0] :escape,
    [1] false,
    [2] [
      [0] :dynamic,
      [1] " value "
    ]
  ],
  [3] [
    [0] :static,
    [1] "."
  ]
]</code></pre><p>Kind of weird, but I can work with this.  At this point we just need to find all values where the first array element (I wish this was structured data and not just positional array fields<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>)  is the symbol <code>:dynamic</code>, and in my research I had already stumbled on a nice <a href="https://gist.github.com/eliotsykes/1470356fd6ed4b18396dae3e26f6f0bb#file-erb_parser-rb-L101">gist</a> of someone gracious enough to share just such a function saving me a little bit of time. Thank you stranger!</p><pre><code>def extract_dynamics(ast)
  dynamics = []
  ast.each do |node|
    next unless node.is_a?(Array)
    if node[0] == :dynamic
      dynamics &lt;&lt; node
    else
      dynamics += extract_dynamics(node)
    end
  end
  dynamics
end</code></pre><p>Once we have all the dynamic blocks we can then map the values and re-render them against the context:</p><pre><code>template = "This is an erb block &lt;%= value %&gt;."

erb_parser = Temple::ERB::Parser.new

ast = erb_parser.call(template)

dynamic_data = extract_dynamics(ast)

all_present =
  dynamic_data.all? do |entry|
    # individually render each component of the erb 
    # template and validate if they are all
    # non blank. This ensures we don't accidentally 
    # have a "nil" or "" value rendered in the final resulting string

    ERB.new("&lt;%= #{entry} %&gt;").result(render_api.bind).present?
  end</code></pre><p>From here we can decision on what to do and prevent accidental errors during dynamic template invocation.</p><p>Because there is no way to catch this at template write time (given it&#8217;s dynamic yml files provided by external parties) we can make the right thing safe by validating it template time.  </p><p>Anytime I work on some sort of platform, SDK, or library, I like to think about how I can minimize errors of the consumer and maximize feedback back to them. Since we know the the ERB sub-block we are evaluating we can even tell the consumer that the particular block is empty, giving them some nice discoverability instead of wondering why something is missing.  </p><p>I&#8217;ve come around, slowly, to the power that dynamic ruby has.  I still much prefer to add sorbet typing to everything as much as possible and to avoid working with untyped hashes, but sometimes dropping into the magic of meta-programming can be really impressive.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/subscribe?"><span>Subscribe now</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>More on template engine first principles can be found at this earlier <a href="https://antonkropp.substack.com/p/simple-template-engine">blog post</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>I find that dynamic language enthusiasts shirk structured data like using a struct/interface/whatever and tend to use positional arguments to imply meaning. In this example it&#8217;s an of Tuples where the format is <code>[node type, node value]</code>. I find this problematic and I would almost always advocate to not use this because the meaning behind the data is <em>implicit</em>.  Without a logical explanation of it it can&#8217;t be self discoverable, and refactoring or changing this is now tied to the array shape.  In any typescript this would probably represented as an discriminated union of types.  Not only can you see what each field means, but the tree and inter-relationships are clear.</p><pre><code>interface DynamicNode {
   type: 'dynamic',
   value: string
}

interface StaticNode {
   type: 'static',
   value: String
}

interface Escaped {
   type: 'escape',
   isEscaped: boolean,
   node: DynamicNode
}

type Node = DynamicNode | StaticNode | Escaped</code></pre><p>Going back to typed arrays I often ask an interview question (more about that in my upcoming book!) that requires people to pair three pieces of data (key, value, time).  Juniors who overly rely on primitive data structures like maps/arrays tend to create an unintelligible mess solving this problem.  And that&#8217;s not because these structures don&#8217;t work, its because they make it confusing and hard to keep track of. <em>People get lost</em> due to the cognitive load they have to put on themselves to know that array[0] is &#8220;type&#8221; and array[1] is &#8220;value&#8221;.  </p><p>Do yourself a favor and just make it an actual data object and things will get a lot simpler.  </p></div></div>]]></content:encoded></item><item><title><![CDATA[Challenges with data]]></title><description><![CDATA[Modeling is hard]]></description><link>https://www.onoffswitch.net/p/challenges-with-data</link><guid isPermaLink="false">https://www.onoffswitch.net/p/challenges-with-data</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Tue, 30 Jan 2024 01:55:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YKQ_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It occurred to me after sending off my book to the editors that I missed a crucial section.  A huge failure mode of moving quick is being stymied by your data model.  I&#8217;d argue that spending major thought and time to how you store and model your data is a true make-or-break scenario. In my book, &#8220;Building A Startup - A Primer For The Individual Contributor&#8221;, I often talk about the concept of one-way streets: decisions that once made are hard to undo and tend to set a direction for your project. When the data model works against you you&#8217;ll find that things that should seemingly be easy become extraordinarily difficult.</p><p>Why is this a problem? Well it&#8217;s because most everything in your application uses data! Data informs the domain model, it determines how classes and data objects are structured, and that then propagates upward into how relationships and code is structured.  If the data is poorly managed then often the higher level API and abstractions become cludgy and difficult to use.  Direct REST API&#8217;s or CRUD from gRPC/Thrift/etc can leak the difficult data model requiring disjoint inputs just to do basic work.  These issues all stem from a database that isn&#8217;t structured well or wasn&#8217;t given enough love and care to be able to properly model the domain it&#8217;s storing.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YKQ_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YKQ_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 424w, https://substackcdn.com/image/fetch/$s_!YKQ_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 848w, https://substackcdn.com/image/fetch/$s_!YKQ_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 1272w, https://substackcdn.com/image/fetch/$s_!YKQ_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YKQ_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png" width="322" height="447.3376344086021" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1292,&quot;width&quot;:930,&quot;resizeWidth&quot;:322,&quot;bytes&quot;:924058,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YKQ_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 424w, https://substackcdn.com/image/fetch/$s_!YKQ_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 848w, https://substackcdn.com/image/fetch/$s_!YKQ_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 1272w, https://substackcdn.com/image/fetch/$s_!YKQ_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15e4f251-1fcf-44e3-b8dc-226b4f743dff_930x1292.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Naming is hard</h2><p>First and foremost is naming.  Names are hard but changing column or field names in a database after the fact can be daunting, especially if the data size is enormous because migrations can be error prone, take a long time, and sometimes require application downtime (which nobody likes to do).  Do the names match the domain they are modeling? Are they descriptive? Short cuts and acronyms aren&#8217;t worth the effort.  </p><p>If its an &#8220;account_id&#8221; then don&#8217;t call it &#8220;acc_id&#8221;.  Long term think about who is going to read this content: other developers, data scientists, you (after you&#8217;ve forgotten everything).   Ensure those names are consistent with other names as well! If you&#8217;re using snake case, keep snake case everywhere. Consistency helps minimize cognitive load, people will learn and understand patterns from the consistent naming conventions.  Don&#8217;t reference &#8220;account_id&#8221; in one table, and &#8220;account&#8221; in another table.  </p><p>When modeling cross domain references I like to use context to prefix a foreign key id. For example, if we have a users table that references an internal and external account id&#8217;s (but both fields are foreign keys to the &#8220;accounts&#8221; table) then we might want to call them &#8220;external_account_id&#8221; and &#8220;internal_account_id&#8221;. </p><pre><code>users
id, name, external_account_id, internal_account_id</code></pre><p>Keeping with a logical convention, however you choose, makes it easy to reason about the data system and to understand what links and relationships data has.  </p><p>Consider also how you name or your foreign keys. Use a consistent naming pattern.  It might be </p><pre><code>fk_&lt;source_table&gt;_&lt;field&gt;_&lt;foreign table&gt;_&lt;field&gt;</code></pre><p>like &#8220;fk_users_external_account_id_accounts_id&#8221; which now tells someone </p><pre><code>users.external_account_id -&gt; accounts.id</code></pre><p>Names aren&#8217;t just important in relational storage either, they have just as much, if not more importance, in non-relational data because in a No-SQL world different key elements can have different shape&#8217;d data.  Shapes are important, and ensuring that the shapes you store are consistent across the semantic type they represent is important. I often say that just because No-SQL is schema-less doesn&#8217;t mean you don&#8217;t enforce a schema. The opposite in fact! You have to enforce the schema in application code now, and without a clear and unified schema managing your data becomes a nightmare. What data is being represented by a specific key/value pair? Is that data correct? What does changing that data mean when you start to add/remove new fields? </p><p>In the No-SQL world I strongly recommend storing data blobs with version and type fields, so that you can disambiguate how data maps to model classes in code.  For example, using a JSON format like below can make your key-value storage data easily consumable and discoverable as well as safe from compatibility mismatches</p><pre><code>{
   version: 1,
   type: "foo",
   data: {
     ... your fields ..
   }
}</code></pre><h2>Sources of truth</h2><p>The next thing I often consider, after agonizing over how to name things, is what the relationship of data is and what to store in a particular table (or data blob).  For example, why are we storing? Do we have to it? What does it mean to store this data? The goal of this thought exercise is to make sure that we&#8217;re storing exactly what we need, we&#8217;re not denormalizing it (or if we are it&#8217;s for good reason), and that adding this particular field creates value for us long term.  I&#8217;ve seen it in in certain places where people store things like </p><pre><code>table
id, name, event_payload, first_name, name_title, name2, event_name</code></pre><p> It seems contrived, but we&#8217;ve probably all seen some travesties of data modeling that look similar.  In this example we have 4 different name types, and presumably some raw data in &#8220;event_payload&#8221; that maybe &#8220;event_name&#8221; is part of. In this example, I&#8217;d argue that we didn&#8217;t think clearly about what it means to store a <em>name</em> and we didn&#8217;t think clearly about what it means to store parts (or all) of an event. Who is the source of truth here?   Not to mention is the name being stored somewhere else as well? Is it cross databases? Is there a centralized way to query and get that data?   </p><p>Without clear sources of truth and ownership questions tend to arise of how you manage and update this data. What do you need to actually do an update? Is the update valid? Do you have to update multiple places at once? All these questions can arise!</p><p>Assuming this is the one and only place this name has to exist,  I&#8217;d probably recommend (in a relational world) <a href="https://dev.mysql.com/doc/refman/8.0/en/create-table-generated-columns.html">virtual columns</a> to project data from a blob to make querying easier, or just store compound data as a JSON blob in a field. For example, if we created a unified name object:</p><pre><code>interface Name {
  given_name: string
  family_name: string
  middle_name?: string
  ..
}</code></pre><p>Then we can pair data together as a group, update it as a group, and query it more sanely.  Without proper pairing of data we might end up having to query or join multiple tables to materialize a person&#8217;s full name. This makes it a difficult and frustrating experience for developers to answer basic questions.</p><h2>Smell tests</h2><p>In general, I like to use a litmus test of &#8220;how easy is it to do basic CRUD and answer domain related questions&#8221;. If your domain has user data split across 4 tables and they are always queried together&#8230; are they really actually separate?  Should it all be flattened in one? Conversely if you have disparate data mashed into one table should it logically be split out and referenced separately?  When CRUD is complex, either by requiring multiple different calls, or complex SQL to be written that&#8217;s signal that something is amiss.  In my experience non trivial queries tend to be limited in scope, and most things do not require more than a handful of joins.  </p><p>I also like to make sure that data is never <em>contextual</em>.  You should never have a field that means X sometimes and Y other times.  Having this kind of implicit codification based on data state creates challenges in reasoning about systems.  If you have conditional data, pair it together as a blob or types or add explicit metadata about what you need to be storing.  Contextual examples might be something like &#8220;sometimes field A is the user id and sometimes it&#8217;s the account id&#8221;.   This happens a lot when designing single table No-SQL schemas where columns get overloaded based on context of what the key is. Woe is the engineer who has to decipher this spaghetti.  </p><h1>Conclusion</h1><p>Working with data is challenging because data often changes over time. The domain you think you are modeling now might not be the domain you end up with over time.  However, data is the most basic layer of abstraction in your system. If the data is hard to work with, all layers above it will be hard to work with.  Giving the data model the respect and diligence it deserves pays off dividends over time.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Design Patterns - Wrappers]]></title><description><![CDATA[Lets talk about wrappers]]></description><link>https://www.onoffswitch.net/p/design-patterns-145</link><guid isPermaLink="false">https://www.onoffswitch.net/p/design-patterns-145</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Sun, 28 Jan 2024 00:16:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PZK6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4cb7844-4d27-476c-bb70-6fe47f478538_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lets talk about wrappers</p><ul><li><p>Proxy</p></li><li><p>Decorator</p></li><li><p>Adapters</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uGMN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uGMN!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 424w, https://substackcdn.com/image/fetch/$s_!uGMN!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 848w, https://substackcdn.com/image/fetch/$s_!uGMN!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 1272w, https://substackcdn.com/image/fetch/$s_!uGMN!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uGMN!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif" width="400" height="275" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:275,&quot;width&quot;:400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!uGMN!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 424w, https://substackcdn.com/image/fetch/$s_!uGMN!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 848w, https://substackcdn.com/image/fetch/$s_!uGMN!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 1272w, https://substackcdn.com/image/fetch/$s_!uGMN!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca3151d8-acad-440a-b22c-4a65342b0374_400x275.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Wrappers usually wrap an object and provide either extra functionality explicity (exposing new methods) or implicity (providing the same contract as the wrapped object). Lets look at some examples. Wrappers come in many names: adapter, decorator, proxy, but they all tend to do the same thing. They wrap something.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Personally I find that this is maybe <em>the most</em> useful pattern out there. It&#8217;s so common I hardly ever think about the names of the patterns anymore. I should note that just because you <em>use</em> a pattern doesn&#8217;t necessitate the name of the pattern being in the class/interface name.</p><h1>Proxy</h1><p>A proxy is a wrapper that takes a thing but gives the contract of the underlying object.</p><p>Lets pretend we have a state trait that defines how to get and put something based on a key.</p><pre><code><code>trait State {
  def save[T](key: String, item: T): Unit
  def get[T](key: String): Option[T]
  def delete(key: State)
}
</code></code></pre><p>We can have a simple implementation of our state:</p><pre><code><code>class InMemoryState extends State {
  private val map = new mutable.HashMap[String, Any]
  
  override def save[T](key: String, item: T): Unit = {
     map.add(key, item)
  }
  
  override def get[T](key: String): Option[T] = {
     map.get(item).map(_.asInstanceOf[T]))
  }
  
  override def delete(key: String): Unit = {
    map.remove(key)
  }
}
</code></code></pre><p>This works great. But what if we want to now add monitoring to our state object. We can obviously put monitoring into our state object:</p><pre><code><code>class InMemoryState extends State {
  private val monitor: Monitoring =  ...
  
  private val map = new mutable.HashMap[String, Any]
  
  override def save[T](key: String, item: T): Unit = {
     monitor.increment("save")
     
     map.add(key, item)
  }
  
  override def get[T](key: String): Option[T] = {
     monitor.increment("get")
  
     map.get(item).map(_.asInstanceOf[T]))
  }
  
  override def remove(key: String): Unit = {
    monitor.increment("remove")
    
    map.remove(key)
  }
}
</code></code></pre><p>But the problem here is for every state implementation we create, we need to make sure we consistently add monitoring to it. Especially problematic is if the state implementation is not owned by us. Maybe this <code>InMemoryState</code> comes from a 3rd party library. How do we add monitoring to it? Enter the proxy.</p><p>The proxy will take an instance of <code>State</code>, but also implement <code>State</code> and <em>proxy</em> calls to the inner object. For each call it will add monitoring itself:</p><pre><code><code>class MonitoringStateProxy(state: State) extends State {
  private val monitor: Monitoring =  ...
       
  override def save[T](key: String, item: T): Unit = {
     monitor.increment("save")
     
     state.save(key, item)
  }
  
  override def get[T](key: String): Option[T] = {
     monitor.increment("get")
  
     state.get[T](item)
  }
  
  override def remove(key: String): Unit = {
    monitor.increment("remove")
      
    state.remove(key)
  }
}
</code></code></pre><p>What we have now is a proxy. Anyone who accepts an instance of <code>State</code> won&#8217;t know, or <em>care</em> that the state they got isn&#8217;t the one they used to use. We can now easily add monitoring to any state implementation!</p><p>This kind of stuff is especially useful for things like</p><ul><li><p>Extra failure handling or fallback functionality on failure</p></li><li><p>Consistent monitoring or logging</p></li><li><p>Controlling access to resources (via permissions/etc)</p></li><li><p>Adding caching to heavy resources</p></li></ul><p>The big win is the fact that the proxy implements the same trait as the underlying resource. This forces the proxy to handle all methods of the trait so if the trait changes, the proxy has to follow suit.</p><h1>Decorator</h1><p>A decorator is like a proxy++. It usually (but not always) implements the source interface but also may implement other interfaces OR just expose extra methods itself.</p><p>Lets make a new state object, but this time lets add some extra methods that let us fix keys based on a prefix:</p><pre><code><code>class FixedState(state: State) extends State { 
  override def save[T](key: String, item: T): Unit = {            
     state.save(key, item)
  }
  
  override def get[T](key: String): Option[T] = {    
     state.get[T](item)
  }
  
  override def remove(key: String): Unit = {   
    state.remove(key)
  }
  
  def saveWithPrefix(prefix: String, key: String, item: T): Unit = {
    save[T](prefix + key, item)
  }
}
</code></code></pre><p>Notice the new <code>saveWithPrefix</code> method. What we&#8217;ve done is taken an object of type <code>State</code> and &#8220;decorated&#8221; it with new functionality. The scala world does this all the time with <code>Rich</code> methods (also known as the <code>Pimping</code> pattern). If we were to rewrite this with the Rich extension method pattern, we&#8217;d do it like:</p><pre><code><code>object StateExtensions {
  implicit class FixedState(state: State) {    
    def saveWithPrefix(prefix: String, key: String, item: T): Unit = {
      state.save[T](prefix + key, item)
    }
  }
}
</code></code></pre><p>To use this, we&#8217;d do:</p><pre><code><code>// this provides the implicit conversion to a `FixedState` object
import StateExtensions._ 

val state: State = ...

state.saveWithPrefix(prefix = "foo", key = "bar", value = 123)
</code></code></pre><p>This effectively acts as a decorator pattern even though it doesn&#8217;t actually implement the same interface.</p><h1>Adapters</h1><p>Adapters are a way to square peg round hole something. Imagine you have two inconsistent interfaces</p><pre><code><code>trait Dog {
  // returns a byte stream of a bark sound
  def bark(): AudioStream
}

trait Cat {
  // returns a byte stream of a cat sound
  def meow(): AudioStream
}
</code></code></pre><p>And you have some code that takes a <code>Dog</code>. But lets say you only have a <code>Cat</code> laying around. Is it possible to transform a cat into a dog in such a way that the code that wants the dog can take a cat? Maybe&#8230;</p><pre><code><code>class CatLikeDog(cat: Cat) extends Dog {
  def bark(): AudioStream = {  
    cat.meow()
  }
}
</code></code></pre><p>Ok, I will admit this isn&#8217;t a great example, a cat isn&#8217;t a dog. But&#8230; it can pass as a dog (albeit a weirdly meowing one)! The idea behind an adapter is to do provide the adaptation mechanism to allow an object to behave like something it&#8217;s not. In a non trivial example the adapter may take several other classes and utilities that help it achieve its goal.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Design Patterns - Visitors]]></title><description><![CDATA[Lets talk about visitors]]></description><link>https://www.onoffswitch.net/p/design-patterns-045</link><guid isPermaLink="false">https://www.onoffswitch.net/p/design-patterns-045</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Sun, 28 Jan 2024 00:15:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fhAk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lets talk about visitors</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fhAk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fhAk!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 424w, https://substackcdn.com/image/fetch/$s_!fhAk!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 848w, https://substackcdn.com/image/fetch/$s_!fhAk!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 1272w, https://substackcdn.com/image/fetch/$s_!fhAk!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fhAk!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif" width="500" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:500,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!fhAk!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 424w, https://substackcdn.com/image/fetch/$s_!fhAk!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 848w, https://substackcdn.com/image/fetch/$s_!fhAk!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 1272w, https://substackcdn.com/image/fetch/$s_!fhAk!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F471d5f8b-1f0e-49c5-a72b-ff318c697e7f_500x600.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Visitor pattern is one that people often times have a hard time grokking. This entire pattern predicates on the fact that a thing called <a href="https://en.wikipedia.org/wiki/Dynamic_dispatch">dynamic dispatch</a> exists. Dynamic dispatch is the fact that overloaded methods are determined <em>at runtime</em> based on the referred to argument type.</p><p>Given that, suppose we have the following base and derived classes:</p><pre><code><code>class Base()
class Derived() extends Base

object Runner {
  def method(base: Base) = println("base")
  def method(derived: Derived) = println("derived")
}
</code></code></pre><p>If we were to call the overloaded methods with the following types:</p><pre><code><code>val derived: Base = new Derived()
val base: Base = new Base()

Runner.method(base)
Runner.method(derived) 
</code></code></pre><p>What do you expect to be printed out? Turns out its</p><pre><code><code>base
base
</code></code></pre><p>This is because the dispatcher selects the method based on the referred type, which we explicitly defined as <code>Base</code>. How do we get this to do what we want which is to call <code>derived</code> for runtime types of <code>derived</code> without changing the variable definition?</p><p>The answer here is double dispatch. We need the method overloading to see the referred type as <code>Derived</code> and not actually as <code>Base</code>. What if we refactor things a bit to look like:</p><pre><code><code>
class Runner {
  def method(base: Base) = {
    println("base")
  }

  def method(derived: Derived) = {
    println("derived")
  }
}

class Base {
  def execute(runner: Runner) = {
    runner.method(this)
  }
}

class Derived extends Base {
  override def execute(runner: Runner) = {
    runner.method(this)
  }
}
</code></code></pre><p>And we do now:</p><pre><code><code>val base: Base = new Base
val derived: Base = new Derived
val runner = new Runner

base.execute(runner)
derived.execute(runner)
</code></code></pre><p>This now prints out:</p><pre><code><code>base
deried
</code></code></pre><p>What&#8217;s the difference? The difference is that in the first example the compiler determined the overloaded method to call because we explicity said that the types were of type <code>Base</code>. To the compiler there&#8217;s no question about what the type is, it doesn&#8217;t care that the runtime value of <code>Base</code> may be <code>Derived</code>. However, in the second example, we forced an <em>instance</em> of <code>Derived</code> to call into the runner so that the overloaded method resolves to the <code>Derived</code> instance and not the <code>Base</code> instance!</p><p>In this scenario, we did a double dispatch. We first dispatched on the <code>Base</code> object calling <code>execute</code>, then in <code>execute</code> we <em>dispatched again</em> back to runner which resolves the correct overloaded method.</p><p>Why is this useful? Double Dispatch is used to invoke an overloaded method where the parameters vary among an inheritance hierarchy [1]. This is especially handy when traversing things like trees or other hierarchies. Imagine we have a really crappy programming language that consists only of if statements, boolean expressions, and brackets:</p><pre><code><code>sealed trait SyntaxNode
case class If(predicate: SyntaxNode, body: SyntaxNode) extends SyntaxNode
case class Block(statements: List[SyntaxNode]) extends SyntaxNode
case class BooleanExpression() extends SyntaxNode
</code></code></pre><p>And we want traverse over a tree of this language starting at a block statement and print out the structure of the language. Later if the structure is OK, we&#8217;ll want to actually run the language. Given we want to iterate over the tree in two <em>separate</em> ways, lets make sure the iteration is decoupled from the implementation of the iteration. First lets define a visitor that defines the overloaded methods we want to handle:</p><pre><code><code>trait SyntaxVisitor {
  def visit(ifNode: If)
  def visit(blocK: Block)
  def visit(boolean: BooleanExpression)
}
</code></code></pre><p>Lets also make sure that syntax nodes are forced to implement a visitor with another trait:</p><pre><code><code>trait SyntaxVisitable {
  def accept(visitor: SyntaxVisitor)
}

sealed trait SyntaxNode extends SyntaxVisitable
</code></code></pre><p>Now all our nodes have to implement an <code>accept</code> method that takes a visitor. The methods will <em>all look the same</em>:</p><pre><code><code>case class If(predicate: SyntaxNode, body: SyntaxNode) extends SyntaxNode {
 override def accept(visitor: SyntaxVisitor) = visitor.visit(this)
}

case class Block(statements: List[SyntaxNode]) extends SyntaxNode {
  override def accept(visitor: SyntaxVisitor) = visitor.visit(this)
}

case class BooleanExpression() extends SyntaxNode {
  override def accept(visitor: SyntaxVisitor) = visitor.visit(this)
}
</code></code></pre><p>Now, we can actually implement a visitor and apply it. Remember, the visitors job is to just be able to properly see the runtime type! It&#8217;s just a way of forcing the method overloading to work the way you want it to</p><pre><code><code>class SimpleVisitor extends SyntaxVisitor {
  override def visit(ifNode: If): Unit = {
    println("Got an if")
    ifNode.predicate.accept(this)
    ifNode.body.accept(this)
  }

  override def visit(blocK: Block): Unit = {
    println("Got a block")
    blocK.statements.foreach(_.accept(this))
  }

  override def visit(boolean: BooleanExpression): Unit = {
    println("Got a boolean expression")
  }
}
</code></code></pre><p>And if we run this:</p><pre><code><code>val language = Block(List(
  If(BooleanExpression(), Block(List(
    BooleanExpression()
  )))
))

new SimpleVisitor().visit(language)
</code></code></pre><p>We get the result of:</p><pre><code><code>Got a block
Got an if
Got a boolean expression
Got a block
Got a boolean expression
</code></code></pre><p>Ok, neat, but so what. The real power in this pattern is that the tree (or inheritance heirarchy) doesn&#8217;t care <em>WHO</em> is iterating over it. Just that someone IS iterating over it. We can make a different visitor that maybe actually runs our language. Or maybe we have one that lints that the syntax is correct. This is actually a really common pattern with language design. You have visitors that parse a syntax tree, you have visitors that compile a syntax tree, and you may have visitors that help you debug and print a syntax tree. This lets you decouple how the tree is walked from the actual tree!</p><p>Visitors are difficult to wrap your head around because you have item A calling item B which calls item A again, and we&#8217;re tricking the runtime into executing the correct overloaded method due to the fact that overloaded methods are determined at runtime by the type of the argument.</p><p>In languages like scala that support pattern matching you don&#8217;t really need to do a visitor pattern since you can force pattern matching on exhaustive traits to do the same kind of thing:</p><pre><code><code>def matchLang(node: SyntaxNode): Unit = {
  node match {
    case If(predicate, body) =&gt;
      println("If!")
      matchLang(predicate)
      matchLang(body)
    case Block(statements) =&gt;
      println("Block!")
      statements.foreach(matchLang)
    case BooleanExpression() =&gt;
      println("Boolean!")
  }
}

matchLang(language)
</code></code></pre><p>But we&#8217;re not really relying on dispatching here, under the hood this is akin to doing instanceOf checks on items. On top of that, if we don&#8217;t specifically close the interface tree with a sealed trait AND we don&#8217;t enable warnings as errors, then it is possible for us to add new items to the node tree and forget to implement visitor overloads for it. However, with a visitor pattern when we add new types we MUST implement the visit method which is a great compile time safety net.</p><p>That said, visitors have other problems. The biggest one is that in order to <em>return a result</em> they tend to mutate themselves. This is because if we wanted to be able to return a result, we have to tell the visitor it can return a result, and all the visito methods must also return that type. This means that we can&#8217;t have generic visitors that do <em>anything</em>. That said, you can totally make your visitor return a type if you are sure the type will always be returned.</p><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>[1] <a href="https://lostechies.com/derekgreer/2010/04/19/double-dispatch-is-a-code-smell/">Double Dispatch is a Code Smell</a></p>]]></content:encoded></item><item><title><![CDATA[Design Patterns - Strategies]]></title><description><![CDATA[Lets talk about strategies]]></description><link>https://www.onoffswitch.net/p/design-patterns-e47</link><guid isPermaLink="false">https://www.onoffswitch.net/p/design-patterns-e47</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Sun, 28 Jan 2024 00:15:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1vv1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lets talk about strategies</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1vv1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1vv1!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 424w, https://substackcdn.com/image/fetch/$s_!1vv1!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 848w, https://substackcdn.com/image/fetch/$s_!1vv1!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 1272w, https://substackcdn.com/image/fetch/$s_!1vv1!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1vv1!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif" width="480" height="354" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:354,&quot;width&quot;:480,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!1vv1!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 424w, https://substackcdn.com/image/fetch/$s_!1vv1!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 848w, https://substackcdn.com/image/fetch/$s_!1vv1!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 1272w, https://substackcdn.com/image/fetch/$s_!1vv1!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20eee475-23ce-4c13-b922-60da7e3b0552_480x354.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The strategy pattern is just a fancy way of saying of providing several implementations to an interface and choosing which implementation to use based on some criteria. To quote wikipedia:</p><blockquote><p><em>It defines a family of algorithms, encapsulates each algorithm, and makes the algorithms interchangeable within that family.</em></p></blockquote><p>A great example is retrying. Retries are common in software. You have transient errors all the time: network connectivity issues, file system locking, timeouts, crashes, etc. Building robust software requires generous use of retries.. up to a point. You don&#8217;t want to retry forever and depending on what you are retrying you may want to retry a few times really quick and then give up, or maybe a couple times really quickly and then slower until a maximum amount of retries occurs, or maybe fast really quick then slow for a while and then peak out and try forever.</p><p>Each of those options is a strategy for how to retry. We can implement a strategy pattern pretty easily and lets do an example with retries.</p><pre><code><code>trait RetryStrategy {
  def execute[T](block: =&gt; T): T
}
</code></code></pre><p>This defines a strategy that given some block of code <code>block</code> that returns some value of type <code>T</code> we&#8217;ll try to do it. If it fails, the strategy will retry until it either succeeds or the strategy gives up and throws an error.</p><p>Lets define a family of implementations:</p><pre><code><code>case class ConsistentWaitRetry(
  maxTimes: Int, 
  waitBetweenFailuresMilli: Int
) extends RetryStrategy { ... }

case object NoRetry extends RetryStrategy { ... }

case class ExponentialRetry(
  maxTimes: Int, 
  waitBetweenFailuresMilli: Int,
  maxWaitTimeMilli: Int
) extends RetryStrategy { ... }
</code></code></pre><p>You can see we now can choose between 1 of these 3 stratgies whenever we want to do a retry. Anyone who needs to retry can accept the <code>RetryStrategy</code> trait and it shouldn&#8217;t care what the strategy <em>actually</em> is, just that its contract says it will retry in some fashion.</p><p>To drive the point home lets implement a simple consistent wait retry that retries every N milliseconds up to Y times</p><pre><code><code>case class ConsistentWaitRetry(
  maxTimes: Int, 
  waitBetweenFailuresMilli: Int
) extends RetryStrategy {
  override def execute[T](block: =&gt; T): T = {
     var timesToRetry = maxTimes
     while(timesToRetry &gt; 0) {
        Try(block) match {
          case Failure(ex) =&gt;
            timesToRetry -= 1 
            Thread.sleep(waitBetweenFailuresMilli)
          case Success(value) =&gt;
            return value
        } 
     }
     
     throw new RetryFailedException()
  }
}
</code></code></pre><p>Now we can use it like:</p><pre><code><code>val strategy: Strategy = ConsistentWaitRetry(
  maxTimes = 10, 
  waitBetweenFailuresMilli = 10
)

strategy.execute {
  throw new RuntimeException("I am always failing!")
}
</code></code></pre><p>And this will retry the block 10 times, waiting 10 milliseconds between each retry until it fails with a hard error.</p><p>If we want to make other strategies, like <code>ExponentialRetry</code> or <code>LinearRetry</code> we can do that. The strategy part of this pattern is <em>choosing</em> which implementation to use given some criteria. Each implementation is a strategy, so however you decide to choose a strategy they are interchangeable due to adhering to a common contract. You can even do things now like:</p><pre><code><code>class Worker(retryStrategy: RetryStrategy) {
  def doWork(): Unit = {
    retryStrategy.execute {
      // some work
    }
  }
}

class WorkerUser(name: String) {
  def doWork: Unit = {
    val strategy: RetryStrategy = name match {
      case "Anton" =&gt; 
        ConsistentWaitRetry(...)
      case "Joel" =&gt;
        ExponentialRetry(...)
      case _ =&gt;
        NoRetryStrategy()
    }
    
    new Worker(strategy).doWork()
  }
}
</code></code></pre><p>Assuming we&#8217;ve built out the corresponding strategies (and we can even build out our own custom ones!) we can now choose, given the criteria of the worker name, which strategy to use. The worker itself doesn&#8217;t <em>care</em> what the strategy is, just that it was given one.</p><p>This makes the strategy pattern really flexible in choosing how to execute something at runtime.</p><p>I often times ask an interview question for candindates to design pacman. One thing people struggle with is abstracting how the ghosts who chase pacman <em>find</em> pacman. This is a great way to provide strategies. Some examples of how a ghost can find pacman include</p><ul><li><p>Naively (randomly moving around)</p></li><li><p>A* (tracking pacman and trying to find the least distance to them)</p></li><li><p>AI (predict pacmans movements based on past movements)</p></li></ul><p>If you abstract how the ghosts look for pacman, you can build a ghost class that is <em>pluggable</em> with how it finds pacman. This means instead of having 10 ghost (sub)classes that each handle how movement occurs, you can have 1 class who is given a strategy. This also is a great example of composition.</p><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Design Patterns - Singletons]]></title><description><![CDATA[Problem 1: Hidden resource problem]]></description><link>https://www.onoffswitch.net/p/design-patterns-336</link><guid isPermaLink="false">https://www.onoffswitch.net/p/design-patterns-336</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Sun, 28 Jan 2024 00:14:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PZK6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4cb7844-4d27-476c-bb70-6fe47f478538_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<ul><li><p>Problem 1: Hidden resource problem</p></li><li><p>Problem 2: Thread safety</p></li></ul><p>Lets talk about singletons</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5oBD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5oBD!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 424w, https://substackcdn.com/image/fetch/$s_!5oBD!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 848w, https://substackcdn.com/image/fetch/$s_!5oBD!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 1272w, https://substackcdn.com/image/fetch/$s_!5oBD!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5oBD!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif" width="320" height="236.8" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:148,&quot;width&quot;:200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!5oBD!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 424w, https://substackcdn.com/image/fetch/$s_!5oBD!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 848w, https://substackcdn.com/image/fetch/$s_!5oBD!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 1272w, https://substackcdn.com/image/fetch/$s_!5oBD!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5899340-7fbb-4b37-8d9e-ce1c35b04522_200x148.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>THERE CAN BE ONLY ONE!!! Ok enough highlander stuff.</p><p>Singletons are where there is only ever <em>one</em> instance of the object in your application. Singletons can be static classes, or lazy instantiated classes that are only ever done once.<br>Good usages for singletons are to encapsulate heavy resources. Some examples may be database abstractions since they tend to have connection pools, or caches because caches are only effective if they&#8217;re actually the same instance, etc.</p><p>For all the good that singletons give, they get a lot of bad press. This is for a few reasons.</p><h2>Problem 1: Hidden resource problem</h2><p>One is that when they are used incorrectly they tend to hide that they are being used. Here&#8217;s what I mean:</p><pre><code><code>// a database singleton
object Database {
  def read()
  def write()
}

class User {
   def getName() = {
      Database.read()
   }
}

class Main {
  new User().getName()
}
</code></code></pre><p>There&#8217;s a few problems here:</p><ol><li><p>Looking at just the signature of <code>User</code> you don&#8217;t <em>know</em> that it uses a database</p></li><li><p>Even if you did know it used a database, how do you test this without providing an actual database? Or a local in memory database? Or a mock database?</p></li></ol><p>Scala makes this a little simpler for us in that we can pass singletons (static classes, aka companion objects) around as instances, but other languages like Java/C#/etc do not. In those languages you tend to &#8220;reach in&#8221; to resources directly without having them passed to you.</p><p>Another way of providing a singleton is to instantiate only one class ever:</p><pre><code><code>object DatabaseProvider {
  // the only database we'll make
  lazy val database = new Database
}
// a database class
class Database {
  
  def read()
  def write()
}

class User(database: Database) {
   def getName() = {
      database.read()
   }
}

class Main {
  new User(DatabaseProvider.database).getName()
}
</code></code></pre><p>In this example we&#8217;re passing a database to the user class. The user class doesn&#8217;t <em>know</em> or <em>care</em> that the database is effectively a singleton. All it cares is that it got a database! Whether we instantiate the database in the companion object, or we provide it with a dependency injection framework (like spring/guice/whatever), or we create one instance of it in <code>Main</code> and pass it around doesn&#8217;t matter anymore. It&#8217;s still effectively the same instance floating around.</p><p>Making singletons by hand without the functionality of keywords like <code>lazy</code> in scala can be tricky, especially if you are lazily making singletons in a threaded environment. For those interested in reading more about that they should read up on <a href="https://en.wikipedia.org/wiki/Double-checked_locking">singleton double checked locking</a></p><h2>Problem 2: Thread safety</h2><p>While that fixes the hidden resource problem, singletons still exhibit other potential land-mines. When you have only one instance of an object in your codebase you need to be very aware of the concurrent usages of your class. If you are in a multi-threaded environment then your singleton <em>must</em> be threadsafe. Without thread safety you risk corruption, especially if your singleton contains any kind state (like a cache).</p><p>In general, it&#8217;s best to avoid singletons for everything but when they are actually needed. In most cases objects are cheap to instantiate and cheap to collect (most objects are very short lived) so don&#8217;t be afraid to make new instances of something when you need to.</p><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.onoffswitch.net/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Design Patterns - Factories]]></title><description><![CDATA[Lets talk about factories]]></description><link>https://www.onoffswitch.net/p/design-patterns-945</link><guid isPermaLink="false">https://www.onoffswitch.net/p/design-patterns-945</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Sun, 28 Jan 2024 00:13:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PZK6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4cb7844-4d27-476c-bb70-6fe47f478538_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lets talk about factories</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!myFQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!myFQ!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 424w, https://substackcdn.com/image/fetch/$s_!myFQ!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 848w, https://substackcdn.com/image/fetch/$s_!myFQ!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 1272w, https://substackcdn.com/image/fetch/$s_!myFQ!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!myFQ!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif" width="350" height="263" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:263,&quot;width&quot;:350,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!myFQ!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 424w, https://substackcdn.com/image/fetch/$s_!myFQ!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 848w, https://substackcdn.com/image/fetch/$s_!myFQ!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 1272w, https://substackcdn.com/image/fetch/$s_!myFQ!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b51084b-620b-4a24-b1c3-0674be6d9cf1_350x263.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Factories make objects. The first thing someone new to factories tends to ask is</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><blockquote><p><em>why use a factory? isn&#8217;t </em><code>new</code> a factory?</p></blockquote><p>Yes, it is! It&#8217;s probably the most &#8220;raw&#8221; factory you have. It&#8217;s a way of creating objects. But sometimes creating objects gets complicated. Or sometimes you want to hide away how something is made and expose a way to get &#8220;new&#8221; instances of something without having to know all the inner details of how to create it.</p><p>Lets build a house:</p><pre><code><code>trait Feature
case object Bed extends Feature
case object Dresser extends Feature
case object Sink extends Feature
case object Stove extends Feature
case object Toilet extends Feature
case object Tv extends Feature
case object Couch extends Feature

case class Dimensions(x: Int, y: Int)

case class Room(
  dimensions: Dimensions,
  features: List[Feature]
)
case class House(  
  rooms: List[Room]
)
</code></code></pre><p>We&#8217;ve built a pretty generic house structure. A house is some set of rooms where each room has some size and set of features. We can build any house we want! But, it might be nice to provide some easy way to build out common rooms:</p><pre><code><code>object HouseFactory {
  def small = Dimensions(10, 10)
  def large = Dimensions(100, 100)
  
  def bedroom(size: Dimensions = small): Room = {
    Room(size, features = List(
      Bed, Dresser
    ))
  }
  
  def kitchen(size: Dimensions = small): Room = {
    Room(size, features = List(
      Sink, Stove
    ))
  }
  
  def bathroom(size: Dimensions = small): Room = {
    Room(size, features = List(
      Sink, Toilet
    ))
  }
  
  def living(size: Dimensions = small): Room = {
    Room(size, features = List(
      Couch, Tv
    ))
  }
}
</code></code></pre><p>Now we have a simple way of creating general rooms in our house. Nothing is stopping us from making a house that is more complicated, but if we want to piece together a 1 bedroom apartament, or a shack in the woods, we can probably do that pretty easily now:</p><pre><code><code>// no bathroom since we have an outhouse
val shack = House(rooms = List(
   HouseFactory.bedroom(),
   HouseFactory.kitchen()
))

val oneBedroom = House(rooms = List(
  Housefactory.bedroom(),
  HouseFactory.kitchen(),
  HouseFactory.bathroom(),
  HouseFactory.living()
))
</code></code></pre><p>The power of a factory comes in providing sane and semantic ways of creating complicated objects and being able to pass around that factory as an object itself! Imagine another level of abstraction so that we can have different house factories depending on the criteria we want:</p><pre><code><code>trait HouseFactory {
  def bedroom(): Room
  def bathroom(): Room
  def kithen(): Room
  def living(): Room
}
</code></code></pre><p>Lets make a fancy house factory with some extra features:</p><pre><code><code>case object Butler extends Feature

class FancyHouseFactory extends HouseFactory {
  val huge = Dimensions(1000, 1000)
  
  // two beds, two sinks
  def bedroom: Room = {
    Room(size = huge, features = List(
      Bed, Bed, Dresser, Sink, Sink
    ))
  }
   
  def bathroom: Room = {
    Room(size = huge, features = List(
      Sink, Sink, Couch, Tv
    ))
  }
  
  def kithen: Room = {
    Room(size = huge, features = List(
        Sink, Sink, Stove, Stove, Tv
      ))
    }
  }
  
  def living: Room = {
    Room(size = huge, features = List(
        Butler, Tv, Couch, Couch
      ))
    }
  }
}
</code></code></pre><p>Oh man, faaaancy. We can now pass around a <code>HouseFactory</code> trait and they&#8217;ll all get fancy houses now. If we want to make a <code>CrappyHouseFactory</code> we could do that too. Once we have all these factories, we can apply things like Strategies to choose with factory to use.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Design Patterns - Composition]]></title><description><![CDATA[Lets talk about composition (over inheritance)]]></description><link>https://www.onoffswitch.net/p/design-patterns-b94</link><guid isPermaLink="false">https://www.onoffswitch.net/p/design-patterns-b94</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Sun, 28 Jan 2024 00:13:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aKmI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lets talk about composition (over inheritance)</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aKmI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aKmI!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 424w, https://substackcdn.com/image/fetch/$s_!aKmI!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 848w, https://substackcdn.com/image/fetch/$s_!aKmI!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 1272w, https://substackcdn.com/image/fetch/$s_!aKmI!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aKmI!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif" width="400" height="338" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:338,&quot;width&quot;:400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aKmI!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 424w, https://substackcdn.com/image/fetch/$s_!aKmI!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 848w, https://substackcdn.com/image/fetch/$s_!aKmI!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 1272w, https://substackcdn.com/image/fetch/$s_!aKmI!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21abf61e-83c8-4287-93b4-797c643d99d0_400x338.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In object oriented programming inheritance is a big deal (pun intended). It defines that objects have heirarchical relationships of an &#8220;is-a&#8221; type. For example a dog &#8220;is-a&#8221; animal. So you can model dog by having an animal class:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><pre><code><code>class Animal()
</code></code></pre><p>and having Dog subclass Animal:</p><pre><code><code>class Dog() extends Animal
</code></code></pre><p>In many cases this makes sense. Having inheritance lets you leverage structural properties like covariance and contravariance and lets you define logical heirarchies.</p><p>Where inheritance falls flat is in code re-use. A lot times you will see code in the wild like:</p><pre><code><code>// defines DB queries
class Database()
class Animal() extends Database
class Dog extends Animal()
</code></code></pre><p>What you see here is that we&#8217;ve defined a dog to be an animal. That makes sense. But an animal is also a&#8230; database?</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mfOl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mfOl!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 424w, https://substackcdn.com/image/fetch/$s_!mfOl!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 848w, https://substackcdn.com/image/fetch/$s_!mfOl!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 1272w, https://substackcdn.com/image/fetch/$s_!mfOl!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mfOl!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif" width="320" height="320" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:190,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!mfOl!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 424w, https://substackcdn.com/image/fetch/$s_!mfOl!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 848w, https://substackcdn.com/image/fetch/$s_!mfOl!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 1272w, https://substackcdn.com/image/fetch/$s_!mfOl!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88c9b7cb-402f-4c04-bbe0-7a2c874f57a1_190x190.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Certainly that doesn&#8217;t make sense to be able to pass an animal to code that expects a database. But, often times you do see heirarchies like this because they look to solve code re-use issues. After all you want to DRY (don&#8217;t repeat yourself). This is especially abusive in languages that support mix-ins or multiple inheritance.</p><p>What we really want here isn&#8217;t an <em>is a</em> relationship, we want a <em>has a</em> relationship. To get ourselves out of <em>is a</em> and into <em>has a</em> we can <em>compose</em> objects of other objects. We can refactor this code smell into a heirarchy that makes sense while still allowing us to re-use code:</p><pre><code><code>// defines DB queries
class Database()
class Animal(db: Database) 
class Dog(db: Database) extends Animal(db)
</code></code></pre><p>Now its clear that a <code>Dog</code> <em>is a</em> <code>Animal</code> and that <code>Animal</code> <em>has a</em> a database. Advantages to this are</p><ul><li><p>Well defined object heirarchies that actually model the problem domain</p></li><li><p>Easily testable components independently (a database can be tested outside of dog/animal)</p></li><li><p>Extensible in you can now add other components and not worry about inheritance conflicts</p></li><li><p>Explicit dependency information</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.onoffswitch.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Anton&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Design Patterns - Intro]]></title><description><![CDATA[Design patterns was a seminal work in codifying elements of resuable object oriented design, but it&#8217;s dry and hard to understand for someone first venturing into the world of patterns.]]></description><link>https://www.onoffswitch.net/p/design-patterns-96b</link><guid isPermaLink="false">https://www.onoffswitch.net/p/design-patterns-96b</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Sun, 28 Jan 2024 00:11:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PZK6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4cb7844-4d27-476c-bb70-6fe47f478538_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="http://www.blackwasp.co.uk/gofpatterns.aspx">Design patterns</a> was a seminal work in codifying elements of resuable object oriented design, but it&#8217;s dry and hard to understand for someone first venturing into the world of patterns. I wanted this document to reflect some (not all) of the patterns that I find are useful in day to day engineering. Some are omitted because they are niche, and others omitted because they are so similar to other patterns that I think the nuances are lost on the uninitiated.</p><p>This site is an attempt at boiling down some of the Gang of Four nomenclature and to provide simple examples that a reader can extrapolate upon. In my own original readings of Gang of Four I was constantly confused about the subtleties of one pattern over another. While Gang of Four is an extremely thorough in analyzing and defining the patterns, I think that c++/smalltalk plus UML diagrams are not necessarily the best way to grok what are extremely valuable tools in the engineers toolbox.</p><p>Some claim that design patterns are a smell, that they are used to cover up warts in your language or tools. I wouldn&#8217;t agree. While I do think that many usages of design patterns are wildly overused, just like with any tool you should recognize when is the right time to use it and when to not.</p><p>While the examples here are written in Scala I don&#8217;t think that the patterns in Gang of Four are mutually exclusive to functional programming. In fact I think they can be used in conjunction with functional paradigms like immutability, recursion, monads, higher kinded types, etc. But in all fairness, some patterns may not translate well to a functional world just like some category theory concepts don&#8217;t translate well to object oriented models. This document won&#8217;t delve into functional concepts and I&#8217;ll leave that for another day.</p><p>In either case, it&#8217;s important to recognize patterns, whether you agree with them or not, because they are part of our shared professional jargon. Understanding the nuances between a decorator and an adapter means you can communicate more clearly to your peers. That said, I don&#8217;t want to encourage anyone to fall into the classical <code>AbstractSingletonProxyFactoryBean</code> joke.</p><p>These design patterns tend to be simple and concise. However, no matter what the pattern is the most common thread among all of them is <em>code to the contract</em>. This means working at abstractions where you don&#8217;t care what the implementation is. This is the biggest challenge I think that engineers just getting started face and when they overcome this hurdle their code tends to become cleaner, shorter, and less error prone.</p><p>In the end, like with all tools, patterns are there to help you go fast, go correct, and go forth. <a href="https://antonkropp.substack.com/t/design-patterns">Let&#8217;s begin</a>.</p>]]></content:encoded></item><item><title><![CDATA[Consistent Hashing]]></title><description><![CDATA[Suppose we want to provide a percentage based rollout of features to a set of users without storing the full set of features for each user.]]></description><link>https://www.onoffswitch.net/p/consistent-hashinghtml</link><guid isPermaLink="false">https://www.onoffswitch.net/p/consistent-hashinghtml</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Tue, 21 Sep 2021 00:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PZK6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4cb7844-4d27-476c-bb70-6fe47f478538_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Suppose we want to provide a percentage based rollout of features to a set of users without storing the full set of features for each user. It may sound overkill, but if you have millions of users and 50 features, suddenly you are talking about storing 50M items and thats no small amount.</p><p>If the user id&#8217;s are evenly distributed in a mathematical space (say they&#8217;re a UUID), you can leverage this to project the distributed ID onto a number line, scale the number to between 0 and 1, and then use that as a flag for if the user gets the feature X or not.</p><p>Lets look at an example:</p><pre><code>import { createHash } from 'crypto';

/**
 * Hashes the value consistently to a space between 0 and 1. Can be used for determining if certain
 * id's have feature flags enabled automatically (for probabilistic rollouts, etc)
 * @param value The value to convert to a number and convert to a 0 to 1 representation on the number line
 * @param offset The offset on the number line to put the hashed value
 * @param scale Default scale is the entire space of md5 numbers
 */
export function consistentHash(value: string, offset = 0, scale = 1): number {
  const buffer = createHash('md5')
    .update(value)
    .digest();

  const hashNum = buffer.readUInt32BE(0);
  
  const maxMd5 = Math.pow(2, 32)
  
  const numberLineMax = maxMd5 * scale

  // scale the hash by the total number space
  return ((hashNum + offset) % numberLineMax) / numberLineMax;
}
</code></pre><p>If our user id <code>6f805e32-592e-46a2-95f3-51826f27e74f</code> we can hash it to a projected value and</p><pre><code>consistentHash('6f805e32-592e-46a2-95f3-51826f27e74f')
</code></pre><p>Gives us: <code>0.8326200970914215</code></p><p>So user <code>6f805e32-592e-46a2-95f3-51826f27e74f</code> is <em>always</em> going to get a feature if its configured to be 83% likely. That&#8217;s kind of cool cause you don&#8217;t need to store the result of this anymore, you can just <em>know</em>.</p><p>Buut, now we have a new problem. What happens to poor ol user X who always hashes to <code>0.01</code>. They get every experiment you throw at them because they&#8217;re constantly in the 1% group.</p><p>This is where the other paramters of our method come into play like <code>offset</code>. Imagine that we&#8217;ve projected the user onto the number line:</p><pre><code>0----X-------------2^32
</code></pre><p>Our 2^32 comes from the fact that we are using md5 to hash the value and the max md5 value is 2^32, which would be the end of our number line.</p><p>We could <em>offset</em> our projected value and then loop it around the number line, to create a consistent but moved percentage</p><pre><code>X+N

0----(X)-----------(X+N)--2^32
</code></pre><p>This now gives us the same distribution but allows our poor user to not always be in the 1% group. How do we choose the offset? Well we can hash any string to get a number, so we can use the experiment/feature ID to do that. Imagine your feature is <code>"uses_new_login_flow"</code> you can hash that to a number N and apply that to the number line.</p><h2>Variants</h2><p>Now that we can hash users to percentages, what if we could then do experiments with variants. For example, imagine we want to see what happens to 20% of our users if we give them one of 3 choices, where each choice is equally likley. Call the choices variants 1, 2, and a control. This happens all the time that we&#8217;d want to experiment on behavior and see what the outcomes are.</p><p>Putting a user into an experiment is easy, we just did that above. But how do we put someone into equal probability variants? If a user falls into 33% they&#8217;ll fall into the other 33% as well.</p><p>Lets walk through an example and see where it leads us.</p><p>Going back to our user<code>6f805e32-592e-46a2-95f3-51826f27e74f</code> who we know maps to <code>0.83</code>. Lets say our experiment is to 50% of our users. Cool, we know this user is going to be in the experiment.</p><p>We now want to test to see if this user gets variant 1, 2 or our control (variant 3). What we want to do is deterministically dice roll this user to fall into 1 of 3 buckets consistently.</p><p>We know that the variants are equally probably, so we have a number line like this:</p><pre><code> 1   2   control
0---|---|---?
</code></pre><p>What we don&#8217;t know is what the end of the number line should be and how to put someone on it.</p><p>What if we re-hashed the user id, but <em>scaled</em> it so that instead of the max number being the max of md5, that we scale it to</p><pre><code>percentage_experiment * max_md5
</code></pre><p>ie.</p><pre><code>0.5 * 2^32
</code></pre><p>We know the user falls into the bucket of 50%, so if we constrain the line to now be 50% of the max we can see where they now fall on <em>that</em> line and then test.</p><p>If we revisit the method we can now do</p><pre><code>consistentHash('6f805e32-592e-46a2-95f3-51826f27e74f', 0, 0.5)
</code></pre><p>which now gives us <code>0.665240194182843</code></p><p>This tells us where on the number line this user would fit GIVEN they were already selected for the past experiment.</p><p>If we wanted to calculate 3 equally likley probabilities, we can divide the number line into probabilities and say</p><blockquote><p>The first is from 0 to 0.33. The second is from 0.33 to 0.66 The third is from 0.66 to 1</p></blockquote><p>So we can now see that <code>6f805e32-592e-46a2-95f3-51826f27e74f</code> would fall into our second variant.</p><p>Doing this calculation is 100% reproducible, is O(1), and doesn&#8217;t require any external storage costs!</p><h2>Conclusion</h2><p>Doing consistent hashing (mapping distributed values to a number line) has a lot of fantastic uses, but does require that you have evenly distributed data to start with. If you have evenly distributed values leveraging consistent hashing can save storage and IO costs quite a bit, at least until you have something more complicated :p</p>]]></content:encoded></item><item><title><![CDATA[JIRA CLI Tooling]]></title><description><![CDATA[I love tooling.]]></description><link>https://www.onoffswitch.net/p/jira-cli-tooling</link><guid isPermaLink="false">https://www.onoffswitch.net/p/jira-cli-tooling</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Tue, 13 Aug 2019 02:33:10 GMT</pubDate><enclosure url="https://onoffswitch.net/wp-content/uploads/2019/08/jira_new.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I love tooling. I am too lazy to do things manually and whenever a process has impedance I have two choices, either don&#8217;t do it, or make it better. I usually try and choose the latter.</p><p>JIRA is the beast everyone loves to hate. Rightfully so frankly, it&#8217;s slow, it&#8217;s tedious, it&#8217;s repetitive. But issue and project trackers like JIRA have <em>value</em>. As you become a more seasoned professional you realize that being able to see what you are working, what <em>others</em> are working on, and the state of the team in general is huge. It means you can plan projects in advance, share status with stakeholders, allow people to see what you&#8217;re doing and keep you from forgetting the things that are in play.</p><p>While other trackers exist (fogbugz, trello, etc) JIRA is by far the most widely used. For the longest time I hated using JIRA, because it didn&#8217;t map to my developer workflow. But lately I&#8217;ve been <a href="https://github.com/devshorts/jira-cli-tooling">using a collection of ruby scripts that wrap the go-jira CLI</a> that makes my life a whole lot easier.</p><p>For example, making a new ticket to track a random idea should be easy:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-sK-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-sK-!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 424w, https://substackcdn.com/image/fetch/$s_!-sK-!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 848w, https://substackcdn.com/image/fetch/$s_!-sK-!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 1272w, https://substackcdn.com/image/fetch/$s_!-sK-!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-sK-!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif" width="754" height="479" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87f53f13-1260-4708-8298-5fc79641041a_754x479.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:479,&quot;width&quot;:754,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-sK-!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 424w, https://substackcdn.com/image/fetch/$s_!-sK-!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 848w, https://substackcdn.com/image/fetch/$s_!-sK-!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 1272w, https://substackcdn.com/image/fetch/$s_!-sK-!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87f53f13-1260-4708-8298-5fc79641041a_754x479.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This fires off a ticket into my teams backlog and gives me a link. Now whenever I think to myself &#8220;it&#8217;d be great to do xyz&#8221; I can just pop open the shell and fire that idea off.</p><p>But we can take this so much further! I really like tying my commits to JIRA so I can see in the JIRA opened PR&#8217;s, merged PR&#8217;s, commits etc. To do that JIRA needs to integrate with github and know about your commits via (usually) a regex that looks for the jira ticket number in branch, commit, or PR titles.</p><p>Since I&#8217;m too lazy to ever remember doing that, I wanted to have my branches auto named with the current JIRA I&#8217;m on. If we have the ticket in the branch name, we can use git commit hooks to auto format commit messages that are prefixed with the JIRA.</p><pre><code># file: .git/hooks/prepare-commit-message-std #!/bin/sh COMMIT\_MSG\_FILE=$1 COMMIT\_SOURCE=$2 SHA1=$3 function detect\_branch\_name() { # of the format username-JIRA-3586/description # get the branch, remove the username, # split by / and take first segment, # split by - and take second 2 segments git branch | grep \* | cut -d ' ' -f2 | sed 's/$USER-//' | cut -d '/' -f1 | cut -d '-' -f2,3 } branch\_name=`detect_branch_name` if [["$branch\_name" =~ "TRAFFIC-"]]; then data=`cat $COMMIT_MSG_FILE` echo "$branch\_name - $data" \&gt; $COMMIT\_MSG\_FILE fi
</code></pre><p>Now if I make a commit like:</p><pre><code>~/src (akropp-JIRA-123/title) $ git commit -am "foo"
</code></pre><p>My git log would look something like</p><pre><code>JIRA-123 - foo
</code></pre><p>What if we could automatically create a JIRA and set it to in progress and add it to my sprint when I make a branch?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JWby!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JWby!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 424w, https://substackcdn.com/image/fetch/$s_!JWby!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 848w, https://substackcdn.com/image/fetch/$s_!JWby!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 1272w, https://substackcdn.com/image/fetch/$s_!JWby!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JWby!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif" width="754" height="479" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:479,&quot;width&quot;:754,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JWby!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 424w, https://substackcdn.com/image/fetch/$s_!JWby!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 848w, https://substackcdn.com/image/fetch/$s_!JWby!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 1272w, https://substackcdn.com/image/fetch/$s_!JWby!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ab26881-ef74-435e-a28d-beade73fafb8_754x479.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Well we can! For example, we can make a new branch that</p><ol><li><p>Creates a JIRA</p></li><li><p>Adds it to my sprint</p></li><li><p>Marks it in progress</p></li><li><p>Git checkouts with a standardized branch name scheme that works with my commit hooks</p></li></ol><p>All in a single one liner! On top of that making a new branch can pull down the open JIRA&#8217;s I have and prompt me to start working on it if some exist! This way anytime I start a branch it&#8217;s automatically tracked. All the random one-offs I do at work now get visibility and I can see at the end of the sprint how much work <em>I really did</em> vs how much work we planned.</p><p>On top of that, imagine I have open JIRAs and my branch names have the JIRA name in them. I can then switch between working tickets easily by resuming work on a ticket</p><p>My code even works when I have multiple branches for the same JIRA, so if I have multiple logical flows for the same JIRA I can resume different subsections easily (great for large migrations!)</p><p>As always source code on my <a href="https://github.com/devshorts/jira-cli-tooling">github</a></p>]]></content:encoded></item><item><title><![CDATA[Typed react native router]]></title><description><![CDATA[Note, a seasoned UI friend of mine told me this is a terrible way to do things and to instead use hooks instead]]></description><link>https://www.onoffswitch.net/p/typed-react-native-router</link><guid isPermaLink="false">https://www.onoffswitch.net/p/typed-react-native-router</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Tue, 06 Aug 2019 04:20:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PZK6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4cb7844-4d27-476c-bb70-6fe47f478538_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Note, a seasoned UI friend of mine told me this is a terrible way to do things and to instead use hooks instead</p></blockquote><p>I decided to give react native a go the last few days, just to try something different. For what it&#8217;s worth I haven&#8217;t done any real front-end work in quite a bit. The last stuff I did was angular 2/(8/a million?) and it was internal dev tooling stuff that was kind of hacky.</p><p>Seems like the javascript world has come quite a distance since back when typescript was still in beta!</p><p>That said, I dove into a sample react native app that I scaffolded out using <a href="https://expo.io/">expo</a>. Then I popped openn IntelliJ and started to play with react-native-paper and react-navigation.</p><p>The first thing I wanted to see how to do properly was to do DI, since in my mind without clearly having DI you can&#8217;t build large scale applications. There&#8217;s a lot of ways to do DI but you need to make sure to not couple your code to its dependencies. Seems like React&#8217;s answer to that is props, contexts, and redux. I haven&#8217;t looked much at redux yet, but my initial reaction to props and context was lackluster. Props smells like a giant property god object to pass around and that never ends well.</p><p>However, typescript does make this simpler and I do kind of like the higher order container stuff.</p><p>After making a little hello world app and playing with the react router, I wanted to see if we could type the router. For example, in all the examples I can see you use</p><pre><code>this.props.navigation.navigate("Home")
</code></pre><p>To navigate around. This screams bad design to me because I can tell already people will start sprinkling in static strings everywhere, running into typos and other hard to track down problems. What I would rather have is</p><pre><code>this.navigation.home()
</code></pre><p>Which is typed, discoverable, and easy to re-use.</p><p>However, it took me quite a bit to figure out how to inject</p><ol><li><p>Capture an instance of props</p></li><li><p>Wrap the navigation object into an object we can type</p></li><li><p>Pass that object to a component</p></li></ol><p>Turns out higher order containers were needed and the magic sauce is here. First I can make my routable class that given an instance of the screen router can do the things it wants to do.</p><pre><code>type RoutableProps = { navigation: NavigationScreenProp\&lt;any, any\&gt; } class Routable { private navigation: NavigationScreenProp\&lt;any, any\&gt;; constructor(navigation: NavigationScreenProp\&lt;any, any\&gt;) { this.navigation = navigation; } home() { console.log("home") this.navigation.navigate("Home") } back() { console.log("back") this.navigation.goBack() } profile() { console.log("profile") this.navigation.navigate("Profile") } }
</code></pre><p>Assuming we have this (somehow) we can use it in our nav bar</p><pre><code>interface NavProps { router: Routable } export class NavBar extends React.Component\&lt;NavProps\&gt; { render() { const navigate = this.props.router; return ( \&lt;Appbar\&gt; \&lt;Appbar.BackAction onPress={() =\&gt; navigate.back()}/\&gt; \&lt;Appbar.Action icon="archive" onPress={() =\&gt; navigate.home()}/\&gt; \&lt;Appbar.Action icon="mail" onPress={() =\&gt; navigate.profile()}/\&gt; \&lt;/Appbar\&gt; ); } }
</code></pre><p>Now here we can wrap a higher order container to capture the props, inject the setting we want, and return a new decorated component</p><pre><code>function withRouter(Component) { // inject the navigation route to the props of the component return withNavigation( class extends React.Component\&lt;RoutableProps\&gt; { render() { const nav = this.props.navigation; // give the component a router return ( \&lt;Component router={new Routable(nav)}/\&gt; ); } } ); } export const Nav = withRouter(NavBar);
</code></pre><p>Now we can safely use our typed navigation component</p><pre><code>export default function Home() { return ( \&lt;PaperProvider theme={theme}\&gt; \&lt;SafeAreaView style={styles.bottom}\&gt; \&lt;Nav/\&gt; \&lt;Title\&gt;Hello\&lt;/Title\&gt; \&lt;/SafeAreaView\&gt; \&lt;/PaperProvider\&gt; ); }
</code></pre><p>And whamo! Typed navigation.</p><p>Full source available at my github <a href="https://github.com/devshorts/react-native-samples">https://github.com/devshorts/react-native-samples</a></p>]]></content:encoded></item><item><title><![CDATA[Tools Matter]]></title><description><![CDATA[I feel like I&#8217;ve written about this before, but it continues to come up in my career.]]></description><link>https://www.onoffswitch.net/p/tools-matter</link><guid isPermaLink="false">https://www.onoffswitch.net/p/tools-matter</guid><dc:creator><![CDATA[Anton Kropp]]></dc:creator><pubDate>Tue, 30 Jul 2019 05:11:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b39ad79-9d59-436f-be63-c804af77b546_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I feel like I&#8217;ve written about this before, but it continues to come up in my career. Tools matter. Without them you can&#8217;t be an effective engineer. When I bring this up sometimes some intrepid person will give me the &#8220;<em>it&#8217;s a poor craftsman</em>&#8221; schpiel, but have you ever met a craftsperson who doesn&#8217;t care deeply about their tools? Ask any woodworker and they will tell you the countless hours fine tuning, honing, sharpening, and improving their tools. Without them, the job will take longer, be done poorer, and cost more.</p><p>In my mind the biggest most important tool of all for an engineer is their IDE. Lots of people have opinions on what flavor editor/IDE they want but given it&#8217;s nearly 2020, I think there are a few critical tooling features that <strong>must work</strong> otherwise they are a non starter.</p><ol><li><p><strong>Go to definition</strong>. It seems crazy to me that I have to spell this out, but code is data. Any editor should be able to <em>semantically</em> tell you the source of a method/class/function/variable. Grep is not that. Languages are context sensitive, and thats why you need a context sensitive system to help navigate it</p></li><li><p><strong>Find references</strong>. Same as above, just like you can find the definition, you need to find who is using it. Again its context sensitive, I don&#8217;t want to someone to convince me that vim ctags or grep are good enough. They. Are. Not.</p></li><li><p><strong>Semantic Autocomplete</strong>. CTAGS are not semantic autocomplete. CTAGS are the type of completion where an editor indexes the current file(s) and autocompletes based on what&#8217;s in the file. I see many developers using this and it pains me to no end. As an engineer my time isn&#8217;t worth reading scaladoc or ruby docs to find what arguments are to <code>map</code>. I need to hit dot and have the IDE tell me. And if I&#8217;m wrong, the IDE should squiggle it and tell me I&#8217;m wrong so I can fix it fast and move on. I&#8217;m not getting paid to be a compiler. That&#8217;s a compilers job.</p></li><li><p><strong>Semantic Rename and Refactor</strong>. Again, semantics are key. Sed is not semantic. You need to be able to do repo wide renames, refactors, moves/etc, with confidence that the AST is still properly intact and semantically rigorous.</p></li><li><p><strong>Integrated debugging and test running</strong>. Context switching to the CLI to run tests, and in particular a single test, is insane. While many would argue that test runners and debuggers can be done out of band, and they&#8217;re not <em>wrong</em>, but they are missing the fact that if you are doing this task hundreds of times a day (easily) then each moment spent typing or context switching adds up. In an incident any of those things that slow you down matter and have a monetary impact.</p></li></ol><p>What do I mean by &#8220;semantic&#8221;? Using common CLI tools like sed, grep, is it possible to disambiguate a search for &#8220;Foo&#8221; in the context of A vs the context of B?:</p><pre><code>class A { def Foo() String } 

class B { def Foo() String }
</code></pre><p>Could you refactor this:</p><pre><code>class C(a A, b B){ 
   def Foo() String { 
     a.Foo() 
     b.Foo() 
   } 
}
</code></pre><p>To be</p><pre><code>class C(a A, b B){ 
   def Foo() String { 
     a.Bar() 
     b.Foo() 
   } 
}
</code></pre><p>You can&#8217;t. And yet these situations arise constantly in day to day engineering. Good code is refactored code, and codebases that don&#8217;t get constantly improved upon rot and fall apart.</p><p>I feel like these requirements are so basic and primitive that I&#8217;m constantly shocked when I encounter organizations that run without them!</p><p>My thoughts are that anytime systems are being built we need to make sure that the tools continue to work with them. Tooling is important not because I&#8217;m too stupid to be a l337 hacker on the CLI, but because I&#8217;m smart enough to know that I don&#8217;t need to waste my time on that.</p>]]></content:encoded></item></channel></rss>