<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Lack of Imagination</title>
    <link>https://lackofimagination.org/</link>
    <description>Recent content on Lack of Imagination</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 24 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://lackofimagination.org/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Time-Travel Debugging: Replaying Production Bugs Locally</title>
      <link>https://lackofimagination.org/2026/02/time-travel-debugging-replaying-production-bugs-locally/</link>
      <pubDate>Tue, 24 Feb 2026 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2026/02/time-travel-debugging-replaying-production-bugs-locally/</guid>
      <description>&lt;p&gt;We&amp;rsquo;ve all had that sinking feeling. There are multiple crash reports from production. We have the exact input parameters that caused the failures. We have the stack traces. Yet, when we run the code locally, it works perfectly.&lt;/p&gt;
&lt;p&gt;We know where it broke, but we can&amp;rsquo;t see why. Was it a race condition? Did a database read return stale data that has since been overwritten? To find the cause, we have to mentally reconstruct the state of the world as it existed milliseconds before the crash. Welcome to debugging hell.&lt;/p&gt;
&lt;p&gt;If we could simply rewind time and watch the code execute exactly as it did for those failed requests, life would be a lot easier.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://lackofimagination.org/2025/12/testing-side-effects-without-the-side-effects/&#34;&gt;Testing Side Effects Without the Side Effects&lt;/a&gt;, we explored a JavaScript Effect System where business logic doesn&amp;rsquo;t execute actions directly. Instead, it returns a description of what it intends to do in the form of a simple &lt;code&gt;Command&lt;/code&gt; object. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;validatePromo&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;cartContents&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Define the side effect, but don&amp;#39;t run it yet
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdValidatePromo&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;checkPromo&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cartContents&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Define what happens with the result
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;promo&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#a6e22e&#34;&gt;promo&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isValid&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt;({...&lt;span style=&#34;color:#a6e22e&#34;&gt;cartContents&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;promo&lt;/span&gt;}) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Invalid promo&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Command&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cmdValidatePromo&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;outputs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [Function&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdValidatePromo&lt;/span&gt;], &lt;span style=&#34;color:#75715e&#34;&gt;// The command waiting to be executed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [Function&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;]             &lt;span style=&#34;color:#75715e&#34;&gt;// What to do after the command finishes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We often compose multiple commands in a pipeline to make the most of our Effect system:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;checkoutFlow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;cartSummary&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;effectPipe&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;fetchCart&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;validatePromo&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#a6e22e&#34;&gt;cartContents&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chargeCreditCard&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cartSummary&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cartContents&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )(&lt;span style=&#34;color:#a6e22e&#34;&gt;cartSummary&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our effect pipeline handles the &lt;code&gt;Success&lt;/code&gt; and &lt;code&gt;Failure&lt;/code&gt; cases automatically. If a function returns &lt;code&gt;Success&lt;/code&gt;, the subsequent function in line will be called. In the case of a &lt;code&gt;Failure&lt;/code&gt;, the pipeline terminates.&lt;/p&gt;
&lt;p&gt;The series of &lt;code&gt;Command&lt;/code&gt; objects generated by the pipeline is then run by an interpreter using &lt;code&gt;runEffect(checkoutFlow(cartSummary))&lt;/code&gt;. Because our business logic consists of pure functions that interact with the world only through data, we can record those interactions simply by adding a few hooks for services like &lt;a href=&#34;https://opentelemetry.io&#34;&gt;OpenTelemetry&lt;/a&gt;. And if we can record them, we can replay them deterministically. Best of all, there&amp;rsquo;s no need to mock a single database or external service.&lt;/p&gt;
&lt;p&gt;When a crash happens, we don&amp;rsquo;t just get an error message. We get a crash log containing the initial input and the execution trace complete with all outputs.&lt;/p&gt;
&lt;p&gt;In the following simplified trace generated by our workflow, a customer uses a 100% off promo code. The business logic calculates the total as $0.00 and attempts to pass it to the payment gateway, but the payment gateway rejects the API call because the minimum charge amount is $0.50, causing a 500 Internal Server Error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;traceLog&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;flowName&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;checkout&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;initialInput&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;userId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;some_user_id&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cart_abc123&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;promoCode&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FREE_YEAR_VIP&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;trace&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cmdFetchCart&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;result&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cart_abc123&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;items&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;annual_subscription&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalAmount&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;120.00&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cmdValidatePromo&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;result&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cart_abc123&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;items&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;annual_subscription&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalAmount&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;120.00&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;isValid&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;discountType&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;discountValue&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cmdChargeCreditCard&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;result&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;invalid_amount&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Amount must be non-zero.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compared to the cryptic stack traces common in imperative code, this execution trace makes the source of the error immediately obvious.&lt;/p&gt;
&lt;p&gt;We can even go ahead and write a quick time-travel function like the one below to replay any execution trace locally, complete with built-in support for detecting time paradoxes!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;timeTravel&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;workflowFn&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;traceLog&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;initialInput&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;trace&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;flowName&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;traceLog&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;format&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JSON&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringify&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;workflowFn&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;initialInput&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;traceIndex&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`Replay started with initial input: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;format&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;initialInput&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;stepName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;anonymous&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Success&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Failure&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`Replay Finished with state: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Failure&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`Error: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;format&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`Result: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;format&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;recordedEvent&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;trace&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;traceIndex&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;recordedEvent&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;`Trace ended prematurely at step &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;traceIndex&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;. Workflow expected command: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;stepName&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;recordedEvent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;command&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;stepName&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#e6db74&#34;&gt;`Time paradox detected! Workflow asked for &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;stepName&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;, but trace recorded &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;recordedEvent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;command&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`Step &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;traceIndex&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;recordedEvent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;command&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; returned &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;format&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;recordedEvent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentStep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;recordedEvent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When we run &lt;code&gt;timeTravel(checkoutFlow, traceLog)&lt;/code&gt;, it will actually exercise our checkout workflow, and produce the following output. With that, we&amp;rsquo;ve successfully executed a production execution trace locally, all without touching any database or external service:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Replay started with initial input: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;userId&amp;#34;: &amp;#34;some_user_id&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;cartId&amp;#34;: &amp;#34;cart_abc123&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;promoCode&amp;#34;: &amp;#34;FREE_YEAR_VIP&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Step 1: cmdFetchCart returned {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;cartId&amp;#34;: &amp;#34;cart_abc123&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;items&amp;#34;: [&amp;#34;annual_subscription&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;totalAmount&amp;#34;: &amp;#34;120.00&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Step 2: cmdValidatePromo returned {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;cartId&amp;#34;: &amp;#34;cart_abc123&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;items&amp;#34;: [&amp;#34;annual_subscription&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;totalAmount&amp;#34;: &amp;#34;120.00&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;isValid&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;discountType&amp;#34;: &amp;#34;%&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;discountValue&amp;#34;: 100
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Step 3: cmdChargeCreditCard returned {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;error&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;code&amp;#34;: &amp;#34;invalid_amount&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;message&amp;#34;: &amp;#34;Amount must be non-zero.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Replay Finished with state: Failure
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;code&amp;#34;: &amp;#34;invalid_amount&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;message&amp;#34;: &amp;#34;Amount must be non-zero.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Time-travel debugging might sound like a complex feature reserved for heavy-duty enterprise tools, but it fundamentally comes down to architectural design; it takes less than 100 lines of code to implement, and that figure includes our Effect System.&lt;/p&gt;
&lt;p&gt;Because every interaction passes through &lt;code&gt;runEffect&lt;/code&gt;, we can easily implement a redaction layer to scrub personally identifiable information, like credit card numbers or emails, before they ever hit the trace log.&lt;/p&gt;
&lt;p&gt;By pushing side effects to the edges and keeping our core logic pure, we gain a deterministic and secure execution trace. As a result, debugging shifts from guessing what might have happened to watching exactly what did happen, all without compromising user privacy.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href=&#34;https://github.com/aycangulez/pure-effect&#34;&gt;pure-effect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discussed on:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=47154962&#34;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Testing Side Effects Without the Side Effects</title>
      <link>https://lackofimagination.org/2025/12/testing-side-effects-without-the-side-effects/</link>
      <pubDate>Sat, 27 Dec 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2025/12/testing-side-effects-without-the-side-effects/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://lackofimagination.org/2025/11/managing-side-effects-a-javascript-effect-system-in-30-lines-or-less/&#34;&gt;Managing Side Effects&lt;/a&gt;, we built a tiny JavaScript Effect System that treats side effects like database calls as data descriptions rather than executable promises.&lt;/p&gt;
&lt;p&gt;In a typical imperative application, business logic and side effects are inextricably linked. For example, when you write &lt;code&gt;await db.checkInventory(...)&lt;/code&gt;, the runtime immediately reaches out to the database.&lt;/p&gt;
&lt;p&gt;Our Effect System works differently. Instead of performing the action, our functions return a &lt;em&gt;description&lt;/em&gt; of the action. When our code needs to check inventory, it doesn&amp;rsquo;t call the database; it returns a plain object instead, which will be executed later by an interpreter. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;checkInventory&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Define the side effect, but don&amp;#39;t run it yet
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdCheckInventory&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;checkInventory&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Define what happens with the result
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;exists&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;exists&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Out of stock&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Command&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cmdCheckInventory&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;outputs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [Function&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdCheckInventory&lt;/span&gt;], &lt;span style=&#34;color:#75715e&#34;&gt;// The command waiting to be executed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [Function&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;]              &lt;span style=&#34;color:#75715e&#34;&gt;// What to do after the command finishes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is all well and good, but this pattern really shines when we have multiple pure functions we compose together. By using an effect pipeline to pass the output of one function to the subsequent one, we generate a syntax tree: a series of &lt;code&gt;Command&lt;/code&gt;, &lt;code&gt;Success&lt;/code&gt;, or &lt;code&gt;Failure&lt;/code&gt; objects.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;processOrderFlow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;effectPipe&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;validateOrder&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;checkInventory&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chargeCreditCard&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#a6e22e&#34;&gt;paymentId&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;completeOrder&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;paymentId&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice that there&amp;rsquo;s no logic in the above declarative code. The logic is hidden inside the functions, where it belongs, and we can immediately see the steps required to place an order.&lt;/p&gt;
&lt;p&gt;Our effect pipeline handles the &lt;code&gt;Success&lt;/code&gt; and &lt;code&gt;Failure&lt;/code&gt; cases automatically. If a function returns &lt;code&gt;Success&lt;/code&gt;, the subsequent function in line will be called. In the case of a &lt;code&gt;Failure&lt;/code&gt;, the pipeline terminates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Look Ma, No Mocks!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In a standard imperative codebase, testing the order flow would require mocking the inventory database and sandboxing the payment gateway API to avoid real charges.&lt;/p&gt;
&lt;p&gt;With our Effect System, &lt;code&gt;processOrderFlow&lt;/code&gt; is a pure function. It returns a syntax tree that represents the order processing, without actually doing it. We can write a unit test that walks through the logic step by step:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [{ &lt;span style=&#34;color:#a6e22e&#34;&gt;itemId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;sku-123&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;amount&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;99.00&amp;#39;&lt;/span&gt;}];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;processOrderFlow&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Verify the intent to check inventory
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cmdCheckInventory&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Simulate a successful inventory check by calling next with &amp;#39;true&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Verify the intent to charge the card
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cmdChargeCreditCard&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Simulate a successful payment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;payment-id-123&amp;#39;&lt;/span&gt;); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Verify the intent to complete the order
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cmdCompleteOrder&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are three major benefits to this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Speed:&lt;/strong&gt; Tests run almost instantly since there are no network requests or database connections.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Determinism:&lt;/strong&gt; We aren&amp;rsquo;t testing database operations, which can be tested separately by calling &lt;code&gt;await runEffect(processOrderFlow(order))&lt;/code&gt; against a test database. We are testing the business logic instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safety:&lt;/strong&gt; There is zero risk of accidentally charging a real credit card during a test run.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By separating the &lt;em&gt;description&lt;/em&gt; of work from the &lt;em&gt;execution&lt;/em&gt; of work, we&amp;rsquo;ve turned complex, async integration tests into simple, synchronous unit tests, and we&amp;rsquo;ve effectively tested side effects without invoking any.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An AI-Native Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the arguments against functional patterns like our Effect System is the overhead of writing command objects and wrapper functions. However, I think it&amp;rsquo;s safe to say this architecture is effectively AI-native.&lt;/p&gt;
&lt;p&gt;Because the business logic is expressed as data rather than imperative code with hidden state, LLMs can generate, refactor, and test these pipelines with great accuracy. An AI coding assistant doesn&amp;rsquo;t need to understand a complex database mocking setup to write a test; it just needs to predict the next object in the sequence. I&amp;rsquo;d argue that choosing an architecture that is easy for both humans and machines to reason about is a competitive advantage.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href=&#34;https://github.com/aycangulez/pure-effect&#34;&gt;pure-effect&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Managing Side Effects: A JavaScript Effect System in 30 Lines or Less</title>
      <link>https://lackofimagination.org/2025/11/managing-side-effects-a-javascript-effect-system-in-30-lines-or-less/</link>
      <pubDate>Mon, 24 Nov 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2025/11/managing-side-effects-a-javascript-effect-system-in-30-lines-or-less/</guid>
      <description>&lt;p&gt;If you look at the source code of a typical application, you will likely find business logic tangled with database calls, HTTP requests firing off in the middle of validation rules, and try/catch blocks sprinkled here and there.&lt;/p&gt;
&lt;p&gt;The biggest casualty of this coupling is testability. How do you test a function that calculates a user&amp;rsquo;s discount? That&amp;rsquo;s easy, right?&lt;/p&gt;
&lt;p&gt;But how do you test a function that calculates a discount, looks up the user in a database to check their loyalty status, and then emails them a coupon? You can&amp;rsquo;t just run the function. You have to spin up a test database, or utilize a mocking library to intercept calls to &lt;code&gt;db.findUser&lt;/code&gt;. Your unit tests stop testing your logic and start testing your mocks. Soon, you find yourself spending more time configuring the test environment than writing the code itself.&lt;/p&gt;
&lt;p&gt;Fortunately, there is another way. It may feel strange at first, but the idea is deceptively simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t do the work right away. Describe the work first.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Think about the difference between cooking a meal and writing a recipe.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Imperative:&lt;/em&gt; You execute the steps one by one: Go to the kitchen. Chop the onions. Stop if you cry. Turn on the stove.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Declarative:&lt;/em&gt; You write down the recipe instead: &amp;ldquo;Step 1: Chop onions. Step 2: Saute.&amp;rdquo; You can hand the recipe to someone else. You can also analyze it to see if it contains allergens (bugs) without cooking it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end of this article, we will have a tiny but functional &amp;ldquo;Effect System&amp;rdquo; in JavaScript. We will stop writing functions that execute side effects and start writing functions that return descriptions of them instead.&lt;/p&gt;
&lt;p&gt;But first, let&amp;rsquo;s look at a typical example of imperative code that executes the following user registration steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate the input.&lt;/li&gt;
&lt;li&gt;Check the database to see if the email exists.&lt;/li&gt;
&lt;li&gt;If it&amp;rsquo;s new, hash the password and save the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;registerUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Invalid email format.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Password too short.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;foundUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;findUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;foundUser&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Email already in use.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userToSave&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;savedUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;saveUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userToSave&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;User Created:&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;savedUser&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;savedUser&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Registration error:&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The code above is perfectly readable, but it has a number of hidden costs. You can&amp;rsquo;t test the logic without mocking the database calls. And if you don&amp;rsquo;t mock, you can&amp;rsquo;t call &lt;code&gt;registerUser&lt;/code&gt; with the same input twice. If you do, it may fail the second time. Finally, logic jumps around via &lt;code&gt;throw/catch&lt;/code&gt;, which introduces invisible control flow. Because any function could potentially throw, you can never look at a block of code and be 100% sure that the next line will actually execute.&lt;/p&gt;
&lt;h3 id=&#34;the-effect-system&#34;&gt;The Effect System&lt;/h3&gt;
&lt;p&gt;Instead of doing the work right away, the Effect System returns an object describing the work, which will be evaluated later.&lt;/p&gt;
&lt;p&gt;We start by defining three basic objects to represent the state of our program:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Success:&lt;/strong&gt; The previous step worked, and here is the result (&lt;code&gt;value&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failure:&lt;/strong&gt; Something went wrong (&lt;code&gt;error&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Command:&lt;/strong&gt; We store the async side effect in &lt;code&gt;cmd&lt;/code&gt; as a function, but &lt;em&gt;we don&amp;rsquo;t run it yet&lt;/em&gt;. If &lt;code&gt;cmd&lt;/code&gt; returns &lt;code&gt;Success&lt;/code&gt;, the function in &lt;code&gt;next&lt;/code&gt; will be run afterwards.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;) =&amp;gt; ({ &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Success&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;) =&amp;gt; ({ &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Failure&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Command&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;) =&amp;gt; ({ &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we need a combinator function called &lt;code&gt;chain&lt;/code&gt; that stitches these effects together. Its job is to take an existing &lt;code&gt;effect&lt;/code&gt; and connect it to the next function (&lt;code&gt;fn&lt;/code&gt;) in the pipeline.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chain&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Success&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// Pass the success value to function fn
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Failure&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// Short-circuit on error
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// Create a new command that chains the command&amp;#39;s result to fn recursively
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chain&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Command&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We will also need a simple &lt;code&gt;effectPipe&lt;/code&gt; helper. This accepts a list of functions and runs them one by one, passing the output of the previous function into the next, but only if the previous one succeeded (thanks to &lt;code&gt;chain&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effectPipe&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (...&lt;span style=&#34;color:#a6e22e&#34;&gt;fns&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fns&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;chain&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To wrap it up, we need one last function: the interpreter. So far, we haven&amp;rsquo;t run anything; we have just built a big object describing what we want to do. Our &lt;code&gt;runEffect&lt;/code&gt; function is the only place where &lt;code&gt;async/await&lt;/code&gt; lives. It loops through our commands and executes them one by one:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;runEffect&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that&amp;rsquo;s it! If you don&amp;rsquo;t quite understand how these all work, it&amp;rsquo;s ok. Let&amp;rsquo;s refactor the original &lt;code&gt;registerUser&lt;/code&gt; function using our Effect system, and see how the resulting code will look like.&lt;/p&gt;
&lt;h3 id=&#34;from-imperative-to-declarative-code&#34;&gt;From Imperative to Declarative Code&lt;/h3&gt;
&lt;p&gt;We first put the validation logic in its own function and return &lt;code&gt;Success&lt;/code&gt; or &lt;code&gt;Failure&lt;/code&gt; objects instead of throwing exceptions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;validateRegistration&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Invalid email format.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Password must be at least 8 characters long.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we handle the database check. We wrap the &lt;code&gt;db.findUserByEmail&lt;/code&gt; execution in a function and pass it as the &lt;code&gt;cmd&lt;/code&gt;. In the &lt;code&gt;next&lt;/code&gt; argument, we define a function that will receive the &lt;code&gt;foundUser&lt;/code&gt; (in the future) and wrap it in a &lt;code&gt;Success&lt;/code&gt; object. Note that nothing is run here. We are simply describing the command and what will happen next once it is run.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;findUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdFindUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;findUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;foundUser&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;foundUser&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Command&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cmdFindUser&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We put the email availability check in its own function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ensureEmailIsAvailable&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;foundUser&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;foundUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Email already in use.&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, the save operation. After we hash the password, just like the read operation earlier, we wrap the &lt;code&gt;db.saveUser&lt;/code&gt; execution in a function and pass it as the &lt;code&gt;cmd&lt;/code&gt;. In the next argument, we define a function that will receive the &lt;code&gt;savedUser&lt;/code&gt; and wrap it in a &lt;code&gt;Success&lt;/code&gt; object. Once again nothing is run here.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;saveUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userToSave&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdSaveUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;saveUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userToSave&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;savedUser&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Success&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;savedUser&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Command&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cmdSaveUser&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, we use &lt;code&gt;effectPipe&lt;/code&gt; to stitch these steps together into a single &amp;ldquo;recipe&amp;rdquo;, so to speak. In the pipeline below, we use arrow functions &lt;code&gt;() =&amp;gt; ...&lt;/code&gt; to capture the original input variable. This ensures we can access the initial data even if the previous step returned a different result (like &lt;code&gt;true&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;registerUserFlow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;effectPipe&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;validateRegistration&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;findUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;), 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;ensureEmailIsAvailable&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;saveUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;) 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, we run our program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;registerUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;runEffect&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;registerUserFlow&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;OK, let&amp;rsquo;s slow down here a bit and reflect on what we have done so far. We haven&amp;rsquo;t just moved things around the code. We have fundamentally changed its architecture. By decoupling the description of the workflow from its execution, we gain two distinct advantages that are often difficult to achieve with standard &lt;code&gt;async/await&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;testing-without-mocks&#34;&gt;Testing without Mocks&lt;/h3&gt;
&lt;p&gt;In our new version, &lt;code&gt;registerUserFlow&lt;/code&gt; is a pure function. It doesn&amp;rsquo;t touch the database; it returns a data structure representing the database interaction. This means we can test our business logic without spinning up a test database or using complex mocking libraries.&lt;/p&gt;
&lt;p&gt;Testing a failure case is as simple as checking the Failure condition:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;bad-email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;123&amp;#39;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;registerUserFlow&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;deepEqual&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Invalid email format.&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can even inspect the intent of the code. We can verify that the code intends to look up a user, without actually doing it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;test@test.com&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;password123&amp;#39;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;registerUserFlow&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cmdFindUser&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;step2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cmdSaveUser&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;free-logging--profiling&#34;&gt;Free Logging &amp;amp; Profiling&lt;/h3&gt;
&lt;p&gt;Because every database call and API request must flow through the &lt;code&gt;runEffect&lt;/code&gt; interpreter, we can handle cross-cutting concerns like logging, performance profiling, and error reporting in exactly one place.&lt;/p&gt;
&lt;p&gt;Instead of polluting the business logic with scattered &lt;code&gt;console.log&lt;/code&gt; statements, we can simply plug these features into the engine. We update the interpreter once, and suddenly the entire application gets smarter.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a quick example of how to log every async operation and measure how long each operation takes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;runEffect&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--- Starting Pipeline ---&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Command&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;performance&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;duration&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;performance&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;toFixed&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`✅ [&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] completed in &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;duration&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; ms`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`❌ [&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] failed:`&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Failure&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--- Pipeline Finished ---&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;effect&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, when we run our unchanged &lt;code&gt;registerUser&lt;/code&gt; code, we get this output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--- Starting Pipeline ---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✅ [cmdFindUser] completed in 12.2 ms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✅ [cmdSaveUser] completed in 32.5 ms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--- Pipeline Finished ---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;the-m-word&#34;&gt;The M Word&lt;/h3&gt;
&lt;p&gt;Those who have a background in functional programming languages like Haskell will notice that we have created not one, but two Monads in just 30 lines of code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Either Monad (Error Handling):&lt;/strong&gt; By distinguishing between Success and Failure, our pipeline automatically manages the &amp;ldquo;Sad Path.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Free Monad (Side Effects):&lt;/strong&gt; By representing our side effects as data objects (&lt;code&gt;Command&lt;/code&gt;) rather than opaque functions, we built a transparent syntax tree of our program. This is what allows us to inspect, test, and interpret our code without actually running it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Monad is a functional programming pattern, and is often treated as a scary word, associated with complex math. But at its core, a Monad is a method to put a value in a container along with some metadata to handle complexity.&lt;/p&gt;
&lt;h3 id=&#34;the-functional-core-and-the-imperative-shell&#34;&gt;The Functional Core and the Imperative Shell&lt;/h3&gt;
&lt;p&gt;What we have built here is essentially a micro-implementation of a classic architectural pattern known as Functional Core, Imperative Shell.&lt;/p&gt;
&lt;p&gt;The goal of this architecture is to push all side effects to the boundaries of our application, leaving the center pure and predictable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Functional Core:&lt;/strong&gt; In our example, &lt;code&gt;registerUserFlow&lt;/code&gt; represents the core. It contains all our important business rules, validation logic, and decision trees. It is 100% pure, has no dependencies, and is trivial to test because it deals only with data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Imperative Shell:&lt;/strong&gt; The &lt;code&gt;runEffect&lt;/code&gt; function acts as the engine of the shell. Along with our database adapters and entry points, it handles the chaotic reality of the outside world.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By strictly separating these two worlds, we minimize the surface area for bugs. The logic that defines &lt;em&gt;what&lt;/em&gt; the application does is safe inside the core, while the logic that defines &lt;em&gt;how&lt;/em&gt; to execute it is isolated in the shell.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href=&#34;https://github.com/aycangulez/pure-effect&#34;&gt;pure-effect&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Little Big Line Array Build</title>
      <link>https://lackofimagination.org/2025/06/little-big-line-array-build/</link>
      <pubDate>Tue, 17 Jun 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2025/06/little-big-line-array-build/</guid>
      <description>&lt;p&gt;After building quite a few &lt;a href=&#34;https://lackofimagination.org/2025/03/little-big-18-subwoofer-build/&#34;&gt;subwoofers&lt;/a&gt; and &lt;a href=&#34;https://lackofimagination.org/2024/12/building-valkyrie-a-compact-high-output-speaker-with-smooth-directivity/&#34;&gt;2-way speakers&lt;/a&gt; over the years, I was looking for something different, and line arrays fit the bill nicely. They use a large number of small-diameter drivers &amp;ndash;sometimes as many as hundreds&amp;ndash; and have some intriguing characteristics such as little loss of volume as you get further away, low distortion, and very slim enclosures.&lt;/p&gt;
&lt;p&gt;Designing one isn&amp;rsquo;t simple, though, since drivers interfere with each other, causing anomalies in both frequency response and sound dispersion. To minimize destructive interference and use it to our advantage, it&amp;rsquo;s vital to minimize driver-to-driver spacing.&lt;/p&gt;
&lt;p&gt;When making line array speakers, some companies use a large number of 1&amp;quot; tweeters coupled with several small mid-bass drivers. Unfortunately, the distance between two 1&amp;quot; tweeters can&amp;rsquo;t be made much smaller than 40 mm, and they don&amp;rsquo;t really go much below 2 kHz without a waveguide anyway. On the other hand, while 2&amp;quot; full-range drivers result in a slightly wider driver-to-driver spacing of around 60 mm, this is a small price to pay given their ability to reproduce frequencies down to 150 Hz, which is particularly useful for a home theater surround speaker use case I have in mind.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve settled on using eight 2&amp;quot; Lavoce FSN020.71F drivers, featuring neodymium magnets and 3/4&amp;quot; voice coils. I could have used more, but in a typical sized room, they should be more than enough.&lt;/p&gt;
&lt;p&gt;The drivers will be arranged in four banks, with all but the center bank&amp;rsquo;s high-frequency responses shaped by 2nd-order low-pass filters. This aims to reduce high-frequency interference and achieve very narrow vertical directivity, providing near-constant volume regardless of listening position (within reason, of course). This is especially useful if the speakers are angled towards the furthest listeners. That way, you wouldn&amp;rsquo;t be blowing the ears of people close to the speakers, something that&amp;rsquo;s only possible with line arrays. More on this later.&lt;/p&gt;
&lt;p&gt;A line array using 2&amp;quot; drivers will never produce enough bass by itself, so it&amp;rsquo;s only natural to couple it with a subwoofer. And since my latest &lt;a href=&#34;https://lackofimagination.org/2025/03/little-big-18-subwoofer-build/&#34;&gt;subwoofer build&lt;/a&gt; had generous rounded edges, I thought adding curves to the slim line array enclosure would not only make it look less like a column, but also help it match the subwoofer&amp;rsquo;s appearance better.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/layered_build_cut_from_mdf_with_cnc_router.jpg&#34;  alt=&#34;The cabinet walls are made from nine layers of 18 mm MDF.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The cabinet walls are made from nine layers of 18 mm MDF.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Curves certainly look nice, but they aren&amp;rsquo;t easy to make. One method for creating a curved enclosure is to form it from layers cut from a sheet of wood. I&amp;rsquo;m not very fond of this technique, as a lot of material goes to waste, but in this case, the enclosure will be very slim, resulting in little waste. Overall, I&amp;rsquo;ve cut nine layers from 18 mm MDF on a CNC router. Some layers have inner lips to mount the speaker baffle and the rear panel, plus one in the middle for central support. I also applied 1/2&amp;quot; roundovers to the speaker cutouts.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/full_range_drivers_rear_mounted_on_baffle.jpg&#34;  alt=&#34;Full-range drivers rear-mounted to the baffle.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Full-range drivers rear-mounted to the baffle.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Eight drivers mean a lot of unsightly screws, which led me to mount the drivers from the back. However, that requires either a removable rear panel or baffle (or both) to access the drivers in the future. Since space is tight, I decided to make both removable by using threaded inserts at the back of the baffle and long screws, inserted from the rear panel, to attach the front and rear together.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/little_big_line_array_baffle.jpg&#34;  alt=&#34;Baffle front view.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Baffle front view.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I painted the baffle and rear panel black and the side walls, which form the shell, white for what I think is a nice contrasting look.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/front-and-rear-panels_inside_view_with_crossover_board.jpg&#34;  alt=&#34;Baffle and rear panel, inside view.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Baffle and rear panel, inside view.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/little-big-line-array-build/little_big_line_array_crossover_uses_frequency_shading.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/little_big_line_array_crossover_uses_frequency_shading.png&#34; alt=&#34;Crossover circuit diagram&#34; /&gt;

There are four banks of drivers, each with two 8-ohm drivers connected in series. The banks are then connected in parallel, resulting in a nominal load of 4 ohms. However, two filters are necessary to flatten the frequency response for all drivers: a low-shelf cut and a notch. These bring the nominal impedance up to 6 ohms.&lt;/p&gt;
&lt;p&gt;From top to bottom, drivers 1 and 8, 2 and 7, 3 and 6, and finally 4 and 5 form each bank. High frequencies are increasingly &lt;em&gt;shaded&lt;/em&gt; for each bank as they move further from the center, which helps reduce destructive interference and control vertical directivity.&lt;/p&gt;
&lt;p&gt;There are two very short ports made from 45 mm inner diameter PVC pipe to extend the low-frequency response.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/little_big_line_array_with_stand_rear_view.jpg&#34;  alt=&#34;Rear of the cabinet on a scrap wood stand.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Rear of the cabinet on a scrap wood stand.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The speaker was originally designed for wall mounting, but I made a quick stand from scrap wood, incorporating the wall mount, so that it could stand on its own.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/line_array_on_subwoofer.jpg&#34;  alt=&#34;Line array speaker placed on a subwoofer.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Line array speaker placed on a subwoofer.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;So, how&amp;rsquo;s the sound? The speaker needs a subwoofer to fill in the missing low end, but once I&amp;rsquo;d dialed in the subwoofer integration, I was greeted with clean, smooth sound, particularly in the mid-range, and the speaker can go loud for sure. At 95 dB/1m, there was no noticeable distortion thanks to the eight drivers working in tandem. As for the controlled vertical directivity, you can tell the upper frequencies decrease in volume when your ears are not on the speaker&amp;rsquo;s center axis. The effect is subtle but quite noticeable, especially when you go increasingly off-axis.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/line_array_measured_outside.jpg&#34;  alt=&#34;Measured outside at ~2 meters from the ground.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Measured outside at ~2 meters from the ground.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;It&amp;rsquo;s difficult to make quasi-anechoic measurements of the line array due to the number of drivers involved. So, the speaker was measured outside, roughly 2 meters from the ground, to minimize reflections.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/little_big_line_array_on_axis_frequency_response.png&#34;  alt=&#34;On-axis frequency response measured outside at 1 meter (gated at 10 ms).&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;On-axis frequency response measured outside at 1 meter (gated at 10 ms).&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The frequency response is mostly within ±3 dB between 150 Hz and 17 kHz. The critical 1 to 5 kHz region, where the ear is most sensitive, is quite flat with only a ±1.5 dB variation. There are some response anomalies starting at 10 kHz since the 2&amp;quot; driver isn&amp;rsquo;t really suited to reproducing these frequencies well, and there are also some destructive effects from nearby drivers. Fortunately, there&amp;rsquo;s very little content at these frequencies.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/little_big_line_array_horizontal_directivity.png&#34;  alt=&#34;Horizontal directivity is wide up until 5 kHz, but narrows somewhat after that.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Horizontal directivity is wide up until 5 kHz, but narrows somewhat after that.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Horizontal directivity is wide at ±60 degrees nominal up until 5 kHz, but it starts to narrow somewhat after that.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/little_big_line_array_vertical_directivity.png&#34;  alt=&#34;Vertical directivity is very narrow as intended and controlled well for the most part.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Vertical directivity is very narrow as intended and controlled well for the most part.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Vertical directivity is near ruler flat at ±15 degrees nominal down to 1 kHz and gradually gets wider below that. This extremely well-controlled and narrow directivity &amp;ndash;if I may say so myself&amp;ndash; allows precise steering of sound, which is especially well suited for applications such as surround sound speakers in a home theater, where some listeners are closer to a speaker than others.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-line-array-build/little_big_line_array_impedance_plot.png&#34;  alt=&#34;The nominal impedance is 6 ohms.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The nominal impedance is 6 ohms.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The impedance and phase plot shows no major issues other than a few minor resonances. The minimum impedance stays above 5 ohms across the band. The bump centered at around 2.3 kHz is due to the notch filter that flattens the response.&lt;/p&gt;
&lt;p&gt;Finally, a complete build video is available below:&lt;/p&gt;
&lt;hr&gt;
&lt;hr&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/TvHvnOsUsiU&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>The Innocent Loop</title>
      <link>https://lackofimagination.org/2025/04/the-innocent-loop/</link>
      <pubDate>Wed, 30 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2025/04/the-innocent-loop/</guid>
      <description>&lt;p&gt;Loops are so common in programming that we often don&amp;rsquo;t give them a second thought, but a buggy loop has potentially infinite destructive power as it mindlessly repeats its task however many times it&amp;rsquo;s asked.&lt;/p&gt;
&lt;p&gt;Back in the good or bad old days, depending on your perspective, the &lt;code&gt;for&lt;/code&gt; loop with its manual index management was often the only game in town, with the occasional &lt;code&gt;while&lt;/code&gt; loop thrown in for good measure. It was our responsibility to make sure those loops actually terminated by using explicit control conditions. These days, though, with functional programming principles in wide use, we enjoy a rich collection of functions that iterate through arrays, maps, sets, etc., making infinite loops rare.&lt;/p&gt;
&lt;p&gt;With power comes responsibility, though. Can you spot what&amp;rsquo;s wrong with the following piece of code that filters application log entries coming from a database?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getLogEntries&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;startDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;endDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;logLevel&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;service&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;serviceName&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;ve guessed potential memory or performance issues, then you&amp;rsquo;re absolutely right. If &lt;code&gt;getLogEntries&lt;/code&gt; returns millions of log entries, the application can run out of memory before we get to the loop. Even if memory isn&amp;rsquo;t exhausted, iterating through a massive array with the &lt;code&gt;filter&lt;/code&gt; method can take a lot of time. This could block JavaScript&amp;rsquo;s own event loop and make the application unresponsive during the filtering process.&lt;/p&gt;
&lt;p&gt;The first solution that comes to mind is to make the database do the filtering, but if we are unable to do that, we can try switching to chunked processing, which should look similar to the code below, assuming that &lt;code&gt;getLogEntries&lt;/code&gt; always returns an array to simplify error handling:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkSize&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;offset&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getLogEntries&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;startDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;endDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;logLevel&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;offset&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkSize&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;results&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(...&lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;service&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;serviceName&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkSize&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;offset&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unfortunately, our code is no longer as neat as it once was, but at least it can now process large amounts of log entries without issues, or can it? If the &lt;code&gt;results&lt;/code&gt; array becomes too large, we still risk memory exhaustion. In cases like these, it might help to impose hard limits on the number of results returned and either return an error or state that only the first X results were returned. Reasonable limits also offer some level of protection against malicious users.&lt;/p&gt;
&lt;p&gt;Performance-wise, the above code can easily make hundreds of separate requests sequentially, which can increase the total processing time quite a bit, especially when those requests are made over a network connection.&lt;/p&gt;
&lt;p&gt;Fortunately, JavaScript has &lt;code&gt;Promise.all&lt;/code&gt; to execute requests in parallel, but since it has no built-in limit for the number of concurrent requests it makes, it&amp;rsquo;s all too easy to accidentally DoS your database server. Using a library like &lt;a href=&#34;https://www.npmjs.com/package/bluebird&#34;&gt;BlueBird&lt;/a&gt; or &lt;a href=&#34;https://www.npmjs.com/package/p-map&#34;&gt;p-map&lt;/a&gt; that allows setting concurrency limits will be a lot safer. In the following example, we assume we can get the total number of log entries reasonably quickly to allow parallel fetching of chunks, with a concurrency limit of 10:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkSize&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;totalEntries&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getTotalLogEntries&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;startDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;endDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;logLevel&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pageCount&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;ceil&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;totalEntries&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkSize&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;allPages&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array.&lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt;({&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pageCount&lt;/span&gt;}, (&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetchPage&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt; =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getLogEntries&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;startDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;endDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;logLevel&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;page&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkSize&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkSize&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;service&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;serviceName&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkedResults&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;allPages&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;fetchPage&lt;/span&gt;, {&lt;span style=&#34;color:#a6e22e&#34;&gt;concurrency&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chunkedResults&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;flat&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Up until now, we&amp;rsquo;ve seen how to process a large number of records without running out of memory and with reasonable performance, but there is one area where we must be extra careful: making destructive changes like database updates inside a loop, which can be difficult to revert if something goes wrong.&lt;/p&gt;
&lt;p&gt;When there&amp;rsquo;s some business logic involved during batch database updates, it&amp;rsquo;s a good idea to have a dry run functionality not only for easier automated testing but also for manually verifying the results of an update before running it for real.&lt;/p&gt;
&lt;p&gt;The following example demonstrates synchronizing employee data coming from an external source, such as a company&amp;rsquo;s Active Directory system, with the user data stored in an application&amp;rsquo;s database. If a user isn&amp;rsquo;t found in the database, a new record is created; if a user&amp;rsquo;s information has changed, such as the branch they belong to, their record is updated; and finally, if a user is no longer working at the company, their record is marked as disabled.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;usersToInsert&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;usersToUpdate&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;usersToDisable&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;externalUsers&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;externalUser&lt;/span&gt; =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;existingUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;users&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;externalUser&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;existingUser&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;changes&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getChanges&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;existingUser&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;externalUser&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;changes&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;usersToUpdate&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;({&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;existingUser&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;changes&lt;/span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;usersToInsert&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;externalUser&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;users&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt; =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;externalUsers&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;usersToDisable&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By storing the necessary data for database operations in their respective arrays, we effectively separate our intent from execution. This dry run approach not only makes testing and verification easier but, perhaps even more importantly, also permits the auditing of past changes, assuming those changes have been logged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While modern loop constructs abstract away manual index management, performance and memory issues can still arise when processing large datasets.&lt;/li&gt;
&lt;li&gt;Offloading work to the database is often the preferred solution. When feasible, leveraging the database&amp;rsquo;s optimization capabilities is generally more efficient.&lt;/li&gt;
&lt;li&gt;Chunked processing is well-suited for handling large datasets. It allows for processing data in manageable portions, mitigating memory and performance issues.&lt;/li&gt;
&lt;li&gt;Parallel execution can improve performance. However, uncontrolled parallelism can easily lead to performance problems.&lt;/li&gt;
&lt;li&gt;Destructive operations in loops require extra caution: Separating intent from execution allows for easier testing and verification.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Little Big 18&#34; Subwoofer Build</title>
      <link>https://lackofimagination.org/2025/03/little-big-18-subwoofer-build/</link>
      <pubDate>Fri, 28 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2025/03/little-big-18-subwoofer-build/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been &lt;a href=&#34;https://lackofimagination.org/2023/02/subwoofers-for-home-use-an-in-depth-guide/#diy-subwoofers&#34;&gt;building subwoofers&lt;/a&gt; for years now, and I&amp;rsquo;ve always used car audio drivers. However, I recently started &lt;a href=&#34;https://lackofimagination.org/2024/12/building-valkyrie-a-compact-high-output-speaker-with-smooth-directivity/&#34;&gt;building speakers&lt;/a&gt; using pro-audio drivers, specifically mid-woofers and horn loaded compression drivers. I love how dynamic and clean-sounding these drivers are, so it was only natural that I decided to give pro-audio subwoofers a try.&lt;/p&gt;
&lt;p&gt;Unlike subwoofer drivers designed for car/home use, which typically favor deep bass output over efficiency, pro-audio subwoofer drivers excel at mid-bass punch that&amp;rsquo;s difficult for the less efficient drivers to match. Pro-audio drivers also feature strong motors that perform well in small enclosures. Their main limitation, however, is reduced output below 40 Hz. But by using multiple pro-audio drivers, it&amp;rsquo;s easy to achieve low-end performance comparable to their car/home audio counterparts, and when it comes to mid-bass punch, there&amp;rsquo;s simply no contest if both are driven with similar power.&lt;/p&gt;
&lt;p&gt;I did some research on pro-audio subwoofer drivers with relatively low Fs (resonant frequency) and high Xmax (maximum linear cone excursion), and the Lavoce SAF184.03 stood out as a promising candidate. It features a beefy 4&amp;quot; voice coil and 1500W power handling. Some might scoff at the Lavoce&amp;rsquo;s 30 Hz Fs and 13 mm Xmax, but keep in mind that I plan to build multiple subwoofers in tiny sealed enclosures &amp;ndash; at least by 18&amp;quot; subwoofer standards, so deep bass output likely won&amp;rsquo;t be an issue, and I&amp;rsquo;ll still be able to keep enclosure sizes in check.&lt;/p&gt;
&lt;p&gt;The smallest enclosure that can house an 18&amp;quot; driver without sacrificing low-end output appears to be a 19&amp;quot; (~48 cm) cube, but rectangular enclosures are boring. I&amp;rsquo;ve built enough of those already. So, I made some quick sketches exploring alternative enclosure shapes. Although the octagon looked promising, the design featuring generous 4&amp;quot; (~10 cm) radius rounded corners was much more appealing to me, even though I knew it would be somewhat challenging to make.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/compact_subwoofer_cabinet_ideas.png&#34;  alt=&#34;Compact subwoofer cabinet ideas.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Compact subwoofer cabinet ideas.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;There are several methods to make wide-radius rounded corners, but kerf-bending a single sheet of wood is probably the most straightforward. You basically make deep parallel cuts in the wood in close succession, wet the cut areas, and then bend the wood around a form. I could have used MDF for everything, but 15 mm Baltic birch plywood not only keeps the enclosure weight in check but also is stronger than MDF &amp;ndash;not to mention the wood grain looks very nice when stained a light color. The braces and baffle were made from 18 mm (~3/4&amp;quot;) MDF.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/kerf_bent_baltic_birch_plywood.jpg&#34;  alt=&#34;Kerf-bent Baltic birch plywood.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Kerf-bent Baltic birch plywood.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I used a biscuit joiner to cut slots in the plywood. The two MDF braces are attached to the plywood using biscuits, essentially serving as the form around which the plywood was bent.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/kerf_bent_plywood_glued_up.jpg&#34;  alt=&#34;Kerf-bent Baltic birch plywood glued up.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Kerf-bent Baltic birch plywood glued up.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The braces also feature circular openings to support the heavy subwoofer motor, which probably weighs close to 15 kg (~33 lbs) by itself.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/subwoofer_motor_brace.jpg&#34;  alt=&#34;The subwoofer motor brace.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The subwoofer motor brace.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The front baffle and rear panel were painted black, and the birch plywood was stained white with a two-component oil. I lined the walls with polyfill sheets.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/cabinet_stained_painted_and_lined_with_polyfill.jpg&#34;  alt=&#34;The cabinet was stained, painted, and lined with polyfill.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The cabinet was stained, painted, and lined with polyfill.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I placed four pillows filled with polyfill inside to improve deep bass response. In total, I used about 1 lb of polyfill per cubic foot. The gross internal volume, before accounting for driver displacement, is approximately 3 cubic feet (~85 liters), which is about 25% smaller than the typical &lt;em&gt;minimum&lt;/em&gt; enclosure size recommended for a standard 18&amp;quot; car/home audio subwoofer.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/driver_and_cabinet.jpg&#34;  alt=&#34;Driver and cabinet side by side.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Driver and cabinet side by side.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;With the addition of polyfill, the subwoofer&amp;rsquo;s resonant frequency decreased from about 61 Hz to 56 Hz. The impedance curve reveals that inductance is well-controlled, allowing this subwoofer to be easily crossed over as high as 500 Hz. The nominal impedance is 8 ohms.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/impedance_curve.png&#34;  alt=&#34;The impedance curve reveals that inductance is well-controlled.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The impedance curve reveals that inductance is well-controlled.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The near-field frequency response indicates a roll off that&amp;rsquo;s less steep than the expected 12 dB/octave for a sealed enclosure, thanks to the driver&amp;rsquo;s strong motor. When placed along a wall in a typical sized room, the subwoofer should deliver flat frequency response down into the mid-twenties, especially after reducing the ample mid-bass output using EQ or  an AVR&amp;rsquo;s room correction capabilities.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/near_field_frequency_response.png&#34;  alt=&#34;Near-field frequency response.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Near-field frequency response.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Cone excursion utilization is close to optimal. With just 550W RMS of power, assuming some amplifier roll off below 10 Hz, the subwoofer reaches about 70% of its maximum linear excursion at 50 Hz, and stays below 100% at lower frequencies. Note that Lavoce specifies around 10 mm of additional excursion beyond Xmax, providing a comfortable safety margin, though with increased distortion.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/excursion_utilization.png&#34;  alt=&#34;Near optimal excursion utilization with just 550W RMS.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Near optimal excursion utilization with just 550W RMS.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Very little amplifier power is needed above 50 Hz with a 100 Hz crossover. No wonder this subwoofer delivers such great mid-bass punch.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/little-big-18-subwoofer-build/actual_amplifier_power_requirement.png&#34;  alt=&#34;Very little amplifier power is needed above 50 Hz with a 100 Hz crossover.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Very little amplifier power is needed above 50 Hz with a 100 Hz crossover.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Finally, a complete build video is available below:&lt;/p&gt;
&lt;hr&gt;
&lt;hr&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/AXCE6S3FUmg&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Lessons from David Lynch: A Software Developer&#39;s Perspective</title>
      <link>https://lackofimagination.org/2025/02/lessons-from-david-lynch-a-software-developers-perspective/</link>
      <pubDate>Sat, 15 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2025/02/lessons-from-david-lynch-a-software-developers-perspective/</guid>
      <description>&lt;p&gt;David Lynch passed away in January 2025, shortly after being evacuated from his Los Angeles home due to the Southern California wildfires. He&amp;rsquo;s perhaps best known for the groundbreaking TV series &lt;a href=&#34;https://en.wikipedia.org/wiki/Twin_Peaks&#34;&gt;Twin Peaks&lt;/a&gt;, which inspired countless shows, including The X-Files, The Sopranos, and Lost.&lt;/p&gt;
&lt;p&gt;Lynch was genuinely a good human being who cared deeply for his actors and crew. He discovered extraordinary talent like Naomi Watts, who had struggled to land a major role in a Hollywood movie after 10 years of auditioning. From the interviews he gave, it quickly becomes apparent that he respected people of all kinds and never put anyone down &amp;ndash; even those who truly deserved it.&lt;/p&gt;
&lt;p&gt;Lynch is famous for refusing to explain his movies. Although not a fan of his previous work, the great film critic Roger Ebert once wrote that &lt;a href=&#34;https://en.wikipedia.org/wiki/Mulholland_Drive_(film)&#34;&gt;Mulholland Drive&lt;/a&gt; remained compulsively watchable while refusing to yield to interpretation.&lt;/p&gt;
&lt;p&gt;While Lynch offered very little in terms of what his movies meant, he was generous in sharing his views on creativity, work, and life in general. As a tribute to Lynch, I&amp;rsquo;d like to share my perspective on his life lessons from a software developer&amp;rsquo;s viewpoint.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ideas are like fish. If you want to catch little fish, you can stay in the shallow water. But if you want to catch the big fish, you&amp;rsquo;ve got to go deeper.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We&amp;rsquo;ve all got hundreds or even thousands of ideas floating around in our brains. But the really big ones are few and far between. Once you catch a good one &amp;ndash;because they&amp;rsquo;re so rare&amp;ndash; write it down immediately, says Lynch. From there, ideas attract other ideas and start to grow from their initial seed state. The final job is to translate those ideas into a medium, whether it&amp;rsquo;s a film, a painting, or software.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The idea is the whole thing. If you stay true to the idea, it tells you everything you need to know, really. You just keep working to make it look like that idea looked, feel like it felt, sound like it sounded, and be the way it was.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Software development is part art, part engineering. We don&amp;rsquo;t build the same software over and over again &amp;ndash; virtually all software is crafted by hand, sometimes with help from AI. If you ask two developers to create a non-trivial program, it&amp;rsquo;s very likely that the programs they produce will be different, even if the functionality is the same. Under the hood, the programming language, data structures, and overall architecture may be completely different. And on the surface, the user interfaces may look nothing alike.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a good habit to listen to what users have to say, but they often can only describe their problems &amp;ndash; they rarely come up with good ideas to solve them. And that&amp;rsquo;s OK. It&amp;rsquo;s our job to find the right ideas, implement them well, and solve tricky problems in a way we, and hopefully the users, will love.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My friend Bushnell Keeler, who was really responsible for me wanting to be a painter, said you need four hours of uninterrupted time to get one hour of good painting in, and that is really true.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Like other creative fields, writing code requires deep concentration. We need to hold complex structures in our minds while working through problems. Switching between coding and other tasks disrupts &lt;em&gt;flow&lt;/em&gt; &amp;ndash; that magical state of mind where we lose track of time and produce code effortlessly. That&amp;rsquo;s why many developers hate meetings &amp;ndash; they are toxic to our productivity.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I believe you need technical knowledge. And also, it&amp;rsquo;s really, really great to learn by doing. So, you should make a film.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Software development is one of those rare fields where a college degree isn&amp;rsquo;t required to succeed. Yes, we should all know the basics, but in my experience, new college graduates often lack the practical knowledge to be effective developers.&lt;/p&gt;
&lt;p&gt;The real learning happens through hands-on experience: building real projects, debugging tricky problems, collaborating with teams, and maintaining code over time. It&amp;rsquo;s crucial to never stop learning, experimenting, and iterating on our craft.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Happy accidents are real gifts, and they can open the door to a future that didn&amp;rsquo;t even exist.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tim Berners-Lee invented the web in 1989, while working at CERN, the European Organization for Nuclear Research. Originally conceived to meet the demand for information sharing between scientists around the world, the web went mainstream within just a few years.&lt;/p&gt;
&lt;p&gt;Linus Torvalds created Git due to a licensing dispute over BitKeeper, the original version control system used for Linux development. The need for a new tool led to Git becoming the most widely used version control system today.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I feel that a set should be like a happy family. Almost like Thanksgiving every day, happily going down the road together.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Be kind to your teammates, don&amp;rsquo;t embarrass them. They may not be perfect, but accept them for who they are. The most important trait of an effective software development team is psychological safety &amp;ndash;that is, team members feel safe to take risks and be vulnerable in front of each other, as corroborated by &lt;a href=&#34;https://rework.withgoogle.com/en/guides/understanding-team-effectiveness&#34;&gt;Google&amp;rsquo;s research&lt;/a&gt; on the subject.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s OK to make mistakes, as long as you learn from them. Knowing that your team has your back when things go south is a wonderful feeling.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most of Hollywood is about making money - and I love money, but I don&amp;rsquo;t make the films thinking about money.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Just like Lynch prioritizes creativity over financial gain, some of the most impactful software projects started with an open source model, and they literally changed the world, such as Linux, PostgreSQL, and Node.js, just to name a few.&lt;/p&gt;
&lt;p&gt;What makes these projects remarkable is that they didn&amp;rsquo;t emerge from corporate boardrooms &amp;ndash; they were built by communities of passionate developers, collaborating across the world.&lt;/p&gt;
&lt;p&gt;Money is just a means to an end. Unfortunately, many get this confused.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;David, thank you for making the world a better place!&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/david_lynch_2007.jpg&#34;  alt=&#34;David Lynch in 2007 via Wikimedia Commons&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;David Lynch in 2007 via Wikimedia Commons&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Discussed on:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/programming/comments/1ipy01t/lessons_from_david_lynch_a_software_developers/&#34;&gt;Reddit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Runtime Diagnostics: Catching Bugs as They Happen</title>
      <link>https://lackofimagination.org/2025/01/runtime-diagnostics-catching-bugs-as-they-happen/</link>
      <pubDate>Mon, 13 Jan 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2025/01/runtime-diagnostics-catching-bugs-as-they-happen/</guid>
      <description>&lt;p&gt;A popular trope in science fiction movies set in space is running diagnostics whenever something goes wrong with a ship&amp;rsquo;s systems. The chief engineer might announce something like, &amp;ldquo;We&amp;rsquo;ve run a level 2 diagnostic on the thermal regulators.&amp;rdquo; This is technobabble of course, but in software development, the closest parallel to starship diagnostics we have is probably automated regression tests. Checking application logs for errors and, although less common, running integrity checks on data stores also come to mind when diagnosing tricky software bugs.&lt;/p&gt;
&lt;p&gt;Automated test suites play a vital role in validating the correctness of code. However, they come with certain drawbacks: they exist separately from the application code they are designed to test, and they can also take a significant amount of time to execute.&lt;/p&gt;
&lt;p&gt;Tests can easily fall out of sync with the application code if they are not maintained well. Shifting some checks from tests into the application code itself is a powerful technique that not only makes tests less brittle but also ensures runtime validation of internal application logic and data structure integrity.&lt;/p&gt;
&lt;p&gt;Perhaps the most common method for implementing internal consistency checks is using assert statements, which throw an exception when an internal condition is violated. In my experience, assertions are most effective when applied systematically rather than in an ad hoc manner. &lt;a href=&#34;https://en.wikipedia.org/wiki/Design_by_contract&#34;&gt;Design by Contract&lt;/a&gt;, a programming approach originally introduced by Bertrand Meyer in connection with his Eiffel language offers such a system.&lt;/p&gt;
&lt;p&gt;To illustrate the principles of Design by Contract, let&amp;rsquo;s create a simple JavaScript program that generates a data structure for building site organization charts, like the one shown below:&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/sample_org_chart.png&#34;  alt=&#34;A sample site organization chart.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;A sample site organization chart.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The following code is probably as bare bones as it gets, with just three methods and virtually no checks other than what is absolutely necessary. A lot of things could go wrong with this code, some of which might not be immediately obvious:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SiteOrganizer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;, { ...&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Set&lt;/span&gt;() });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;removeSite&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;delete&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;delete&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getSites&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here&amp;rsquo;s how we add the sites in our sample organization chart:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mySites&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SiteOrganizer&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HQ&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Regional Office A&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Regional Office B&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Local Site A1&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Local Site A2&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Local Site B1&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Local Site B2&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;preconditions&#34;&gt;Preconditions&lt;/h4&gt;
&lt;p&gt;The first thing we will add are preconditions, which consist of assertions that enforce the assumptions a particular method makes about the input it receives. These checks explicitly document the conditions that must be true for the method to function as intended, such as ensuring that a site&amp;rsquo;s parent cannot be itself.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cond&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cond&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SiteOrganizer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;`Site &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; should not have been added before.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;`Site &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; cannot be its own parent.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;`Parent site of &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; must exist.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;removeSite&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;`Site #&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; must exist.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;size&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;`Site #&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; must have no children.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getSites&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One can also use regular if-else blocks to test the negative versions of conditions instead of assertions. However, a major advantage of assertions is that they can be easily disabled in production, especially in performance-critical parts of the code, as not all assertions will be as fast as the ones shown above.&lt;/p&gt;
&lt;h4 id=&#34;postconditions&#34;&gt;Postconditions&lt;/h4&gt;
&lt;p&gt;Postconditions are the guarantees a method provides to its caller when all preconditions are met. In the example below, the &lt;code&gt;addSite&lt;/code&gt; method ensures that the site has been successfully added.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SiteOrganizer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;, { ...&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Set&lt;/span&gt;() });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;`Added site &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;`Added site &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; as sub site.`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s not always practical to test all possible outcomes, this is where test suites excel. However, using postconditions whenever possible clarifies what a method&amp;rsquo;s output should be. Even better, postconditions ensure the outcome is tested in runtime.&lt;/p&gt;
&lt;h4 id=&#34;class-invariants&#34;&gt;Class Invariants&lt;/h4&gt;
&lt;p&gt;Class invariants can be thought of as guarantees about the consistency of an object&amp;rsquo;s state. They must hold true between calls to public methods. Code within functions is allowed to temporarily break invariants, as long as they are restored before the function ends. In the example below, all the sites stored in &lt;code&gt;siteMap&lt;/code&gt; are checked to ensure they satisfy certain conditions.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SiteOrganizer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;diagnostics&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;checkChildren&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteId&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;`Site &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; cannot be its own parent.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;`Parent site of &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; must exist.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;checkChildren&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;`Site &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; must have valid children.`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since JavaScript doesn&amp;rsquo;t natively support class invariants (or object invariants in this case), you can call the &lt;code&gt;diagnostics&lt;/code&gt; method at the end of each public method or use a library to automate invariant checks (more on this later).&lt;/p&gt;
&lt;h4 id=&#34;do-we-really-need-all-these-checks&#34;&gt;Do we really need all these checks?&lt;/h4&gt;
&lt;p&gt;The short answer is, probably not&amp;ndash;at least not in every part of the codebase. Of the three, preconditions are the most important, and most developers implement them in some form or another. Postconditions are particularly useful in mission-critical parts of the code, ensuring that methods conform to their specifications.&lt;/p&gt;
&lt;p&gt;As for class invariants, it might seem wasteful to check an object&amp;rsquo;s internal state every time a method is called. However, these checks can save significant time when debugging tricky state-related bugs, and they can be disabled in production code.&lt;/p&gt;
&lt;h4 id=&#34;automating-invariant-checks&#34;&gt;Automating invariant checks&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;If you get the data structures and their invariants right, most of the code will write itself.&amp;rdquo; &amp;ndash; L. Peter Deutsch&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are a few JavaScript libraries that add support for automated invariant checks, such as &lt;a href=&#34;https://github.com/codemix/babel-plugin-contracts&#34;&gt;this one&lt;/a&gt;, but I haven&amp;rsquo;t found a library that works without requiring code transpilation, so I created my own: &lt;a href=&#34;https://github.com/aycangulez/object-diagnostics&#34;&gt;Object Diagnostics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using JavaScript proxies, Object Diagnostics intercepts all method calls and property accesses, including modifications and deletions. It expects a public method named &lt;code&gt;diagnostics&lt;/code&gt; to be defined on the object, which contains the invariant checks. After each intercepted operation, it automatically calls this method.&lt;/p&gt;
&lt;p&gt;Object Diagnostics also includes an environment-aware assert function, but you can use another library for assertions as well.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;ObjectDiagnostics&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;assert&lt;/span&gt; } &lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;object-diagnostics&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mySites&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ObjectDiagnostics&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;addTo&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SiteOrganizer&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;bonus-avoiding-inadvertent-mutations&#34;&gt;Bonus: Avoiding inadvertent mutations&lt;/h4&gt;
&lt;p&gt;Careful readers may have already noticed a fundamental flaw in our example program: its internal data structure, &lt;code&gt;siteMap&lt;/code&gt;, though technically a private property, can still be mutated. Here&amp;rsquo;s a quick example of how:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Set the parent id of &amp;#39;HQ&amp;#39; to a non-existent id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;mySites&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getSites&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When we return &lt;code&gt;siteMap&lt;/code&gt; from &lt;code&gt;getSites&lt;/code&gt;, JavaScript doesn&amp;rsquo;t create a copy; it shares a reference instead. This makes it possible to directly modify the object. However, as soon as another operation is performed on &lt;code&gt;mySites&lt;/code&gt;, the diagnostics method will detect the integrity issue and throw an error.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;mySites&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Local Site B3&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Error: Parent site of HQ must exist.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Even with invariant checks in place, it&amp;rsquo;s a good idea to use copies of objects whenever possible. A utility function like Lodash&amp;rsquo;s &lt;a href=&#34;https://lodash.com/docs/#cloneDeep&#34;&gt;cloneDeep&lt;/a&gt; can help with this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;cloneDeep&lt;/span&gt; } &lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lodash-es&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SiteOrganizer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addSite&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;, { ...&lt;span style=&#34;color:#a6e22e&#34;&gt;cloneDeep&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;site&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Set&lt;/span&gt;() });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getSites&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cloneDeep&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;siteMap&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;summary&#34;&gt;Summary&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Preconditions verify that inputs meet the required conditions before a method executes, while postconditions ensure that the outputs are correct after the method completes.&lt;/li&gt;
&lt;li&gt;Class invariants guarantee the consistency of an object&amp;rsquo;s internal state across its life cycle.&lt;/li&gt;
&lt;li&gt;Runtime diagnostics complement test suites by catching integrity issues while a program runs rather than relying solely on separate tests.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Building Valkyrie: A Compact High-Output Speaker with Smooth Directivity</title>
      <link>https://lackofimagination.org/2024/12/building-valkyrie-a-compact-high-output-speaker-with-smooth-directivity/</link>
      <pubDate>Thu, 12 Dec 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/12/building-valkyrie-a-compact-high-output-speaker-with-smooth-directivity/</guid>
      <description>&lt;p&gt;Well-made hi-fi speakers sound great at low to moderate volumes, but many begin to suffer at high volume levels. Since hi-fi speakers are frequently used without a subwoofer, their designers often have to make certain trade-offs. To achieve reasonable bass, they have to use inefficient drivers that tend to distort at high volumes, assuming there is enough amplifier power before reaching that stage. Even some large floorstanding models with multiple bass drivers aren&amp;rsquo;t immune. The same applies to high-frequency drivers; many dome tweeters can&amp;rsquo;t handle high volume levels and distort noticeably.&lt;/p&gt;
&lt;p&gt;In my opinion, music and especially home theater playback should be done at moderate to high levels to do the content justice. With subwoofers, speakers are relieved of low-bass duties and can concentrate on reproducing the rest of the frequency band. This allows the use of efficient drivers that require significantly less amplifier power to get loud. The pro-audio world has been using such drivers in their speakers for decades, and at a cinema or concert, what you are listening to is obviously not hi-fi speakers.&lt;/p&gt;
&lt;p&gt;I previously &lt;a href=&#34;https://lackofimagination.org/2024/07/building-valeria-a-compact-horn-loaded-speaker/&#34;&gt;built Valeria&lt;/a&gt;, a compact bookshelf speaker using pro-audio drivers, namely a 6.5&amp;quot; mid-bass and a 1&amp;quot; horn-loaded compression driver. The dynamics offered by this small speaker are impressive, if I may say so myself. It&amp;rsquo;s currently serving as the center channel in my home theater.&lt;/p&gt;
&lt;p&gt;Lately, I&amp;rsquo;ve been thinking of building speakers with better bass response and improved horizontal and especially vertical directivity. This necessitated using an 8&amp;quot; mid-bass driver, a compression driver that rolls off below 1 kHz, and a horn that can control directivity better than the B&amp;amp;C ME10 I used in Valeria.&lt;/p&gt;
&lt;p&gt;The mid-bass driver I selected for Valkyrie is the Lavoce WAF082.00. It has a 2&amp;quot; voice coil, a paper cone, and a cast-aluminum basket. The compression driver, Lavoce DF10.171K, features a 1.75&amp;quot; voice coil and a polyimide diaphragm. As for the horn, I decided to use the RCF HF101, a cast aluminum horn with relatively compact dimensions, which is important to minimize driver-to-driver spacing and consequently improving vertical directivity.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/drivers_used.jpg&#34;  alt=&#34;8&amp;#34; mid-bass and 1&amp;#34; horn-loaded compression drivers are used.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;8&amp;#34; mid-bass and 1&amp;#34; horn-loaded compression drivers are used.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;h4 id=&#34;cabinet&#34;&gt;Cabinet&lt;/h4&gt;
&lt;p&gt;I designed a compact cabinet with internal bracing that couples to the drivers, making the drivers part of the brace and reinforcing the front and rear sides of the cabinet.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/valkyrie_3d_model.png&#34;  alt=&#34;Valkyrie 3D model&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Valkyrie 3D model&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I cut the pieces with my CNC router using 18 mm (~3/4&amp;quot;) MDF and then added 45-degree bevels to all sides on &lt;a href=&#34;https://www.youtube.com/watch?v=8LiYrZ3O3TE&#34;&gt;my router table&lt;/a&gt;. I doubled the thickness of the front baffle to make it stronger since the mid-bass driver weighs a healthy 4 kg (~9 lb).&lt;/p&gt;
&lt;p&gt;The pieces are simple enough that they can be cut with a circular saw or jigsaw, and the holes can be cut with a handheld router. There is no need to flush mount the horn or the mid-bass driver. The bevels are optional as well.&lt;/p&gt;
&lt;p&gt;I glued everything together using wood glue, aligning the parts with painter&amp;rsquo;s tape. I recommend using band clamps if you have some at hand.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/gluing_sides.jpg&#34;  alt=&#34;Gluing sides using band clamps.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Gluing sides using band clamps.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The braces were made of 16 mm beech plywood. I attached the braces to the cabinet walls with 3M VHB tape instead of regular wood glue. The soft layer in between acts as constrained-layer damping to reduce resonances.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/inside_view_of_braces.jpg&#34;  alt=&#34;Dry-fitting braces.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Dry-fitting braces.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I glued the port, made from a PVC pipe (68 mm inner diameter), to the back wall with hot glue. The crossover board is installed on the side wall with standoffs.&lt;/p&gt;
&lt;p&gt;I placed M6 threaded inserts into the front baffle so that I could remove and install the drivers as many times as needed without having to worry about stripping the holes, which is quite easy with MDF.&lt;/p&gt;
&lt;h4 id=&#34;crossover&#34;&gt;Crossover&lt;/h4&gt;
&lt;p&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/building-valkyrie-speaker/crossover_circuit_diagram.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/crossover_circuit_diagram.png&#34; alt=&#34;Crossover circuit diagram&#34; /&gt;

I decided to use a third-order low-pass filter with two laminated silicon steel core inductors (2.5 mH and 0.56 mH) in series and a 47 µF capacitor in parallel for the mid-bass driver.&lt;/p&gt;
&lt;p&gt;For the compression driver, I used a second-order high-pass filter with an 8.2 µF capacitor in series and a 2.5 mH air core inductor in parallel.&lt;/p&gt;
&lt;p&gt;I also added an L-pad circuit with a 10 ohm resistor in series and a 4.7 ohm resistor in parallel to reduce the level of the super efficient compression driver by 12 dB on average and flatten its response.&lt;/p&gt;
&lt;p&gt;Finally, I used two parallel notch filters, each consisting of an air core inductor, a capacitor, and a resistor in series, to further flatten the compression driver&amp;rsquo;s response. The notch filter with the lower component values is optional if a peak at 18 KHz is acceptable since there is little content in music at that frequency and many people can&amp;rsquo;t hear it anyway.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/crossover_close_up.jpg&#34;  alt=&#34;Crossover board close-up view. The 2.5 mH air core inductor isn&amp;#39;t pictured.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Crossover board close-up view. The 2.5 mH air core inductor isn&amp;#39;t pictured.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;h4 id=&#34;painting--acoustic-damping&#34;&gt;Painting &amp;amp; Acoustic Damping&lt;/h4&gt;
&lt;p&gt;I applied 19 mm (3/4&amp;quot;) roundovers to all sides using my router table. Then, I took the speaker outside and painted it with several coats of MDF primer using a spray gun. Once the primer had dried, I sprayed several coats of top coat (steel blue). Since I was using water-based paints, I added some paint conditioner (pouring medium) to help reduce orange peel.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/cabinet_painted.jpg&#34;  alt=&#34;Cabinet Painted&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Cabinet Painted&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;It&amp;rsquo;s critical to add absorption material to manage the reflections and standing waves that form inside the cabinet. I lined the walls with felt and placed extra-thick 60 mm pyramid foam on top while leaving enough space for the drivers.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/absorption_materials_placed.jpg&#34;  alt=&#34;Absorption materials placed in cabinet.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Absorption materials placed in cabinet.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Finally, Valkyrie is complete, standing next to Valeria. Valkyrie&amp;rsquo;s dimensions are 24 x 42 x 21.5 cm (WxHxD), while Valeria measures 19 x 33 x 22.5 cm (WxHxD).&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/valkryie_next_to_valeria.jpg&#34;  alt=&#34;Valeria next to Valkyrie.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Valeria next to Valkyrie.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;h4 id=&#34;measurements&#34;&gt;Measurements&lt;/h4&gt;
&lt;p&gt;So, how is the sound? By itself, the bass is lacking, as expected. However, as soon as you add a subwoofer to the mix, the missing low-end is filled in, and what you get is effortless dynamics throughout the entire frequency range assuming the subwoofer can keep up with Valkyrie at higher volumes.&lt;/p&gt;
&lt;p&gt;In listening tests, it&amp;rsquo;s immediately apparent that, compared to Valeria, the bass is considerably louder and deeper below 200 Hz. Otherwise, they&amp;rsquo;re well matched.&lt;/p&gt;
&lt;p&gt;The impedance plot looks clean, with the exception of a resonance at around 300 Hz. This appears to be inherent to the mid-bass driver&amp;rsquo;s construction, as the free-air impedance measurements of the driver show it as well, albeit not as pronounced. The nominal impedance is 6 ohms dipping to 4.4 ohms at 210 Hz.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/impedance_and_phase_plot.png&#34;  alt=&#34;Impedance and phase plot.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Impedance and phase plot.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The frequency response is within ±3 dB from 100 to 20,000 Hz (quasi-anechoic measurement). The mid-bass to compression driver crossover frequency is quite low at 1 kHz to help make vertical directivity well behaved. Please note that it&amp;rsquo;s not advisable to use such a low crossover frequency for this particular compression driver in a commercial setting where speakers are routinely run at their limits, but in a residential setting, it should be fine.&lt;/p&gt;
&lt;p&gt;As for the subwoofer crossover frequency, 100 Hz looks like a good starting point. You can probably use a slightly lower crossover frequency if you place the speaker close to walls, as this will result in some bass boost from nearby boundaries. It&amp;rsquo;s also a good idea to run the subwoofer(s) a few dB hot for optimal integration.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/quasi_anechoic_frequency_response.png&#34;  alt=&#34;On-axis quasi-anechoic frequency response.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;On-axis quasi-anechoic frequency response.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The horizontal directivity is ±45 degrees nominal, smooth down to 800 Hz. Below that frequency, the directivity quickly becomes omnidirectional. Thanks to the uniform directivity, the frequency response can be equalized without any issues.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/valkyrie_horizontal_directivity.png&#34;  alt=&#34;Smooth horizontal directivity (normalized).&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Smooth horizontal directivity (normalized).&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The vertical directivity is ±35 degrees nominal, and it&amp;rsquo;s smooth down to a respectable 1200 Hz. As long as your ears aren&amp;rsquo;t below around 15 degrees of the horn&amp;rsquo;s center axis, the timbre of the speaker shouldn&amp;rsquo;t change noticeably regardless of where you sit or stand.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valkyrie-speaker/valkyrie_vertical_directivity.png&#34;  alt=&#34;Smooth vertical directivity down to 1200 Hz (normalized).&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Smooth vertical directivity down to 1200 Hz (normalized).&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Finally, a complete build video is available below:&lt;/p&gt;
&lt;hr&gt;
&lt;hr&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/1_G4DR4R-kY&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Writing Composable SQL using Knex and Pipelines</title>
      <link>https://lackofimagination.org/2024/11/writing-composable-sql-using-knex-and-pipelines/</link>
      <pubDate>Mon, 25 Nov 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/11/writing-composable-sql-using-knex-and-pipelines/</guid>
      <description>&lt;p&gt;SQL is great! It can handle complex queries involving multiple tables, aggregations, subqueries, and joins. It can perform CRUD (Create, Read, Update, Delete) operations. It enforces data integrity through constraints like primary and foreign keys. It supports transactions, allowing multiple operations to be executed as a single unit, which can be rolled back if one of the operations fails.&lt;/p&gt;
&lt;p&gt;Despite all its strengths, which are too many to list here, SQL can be awkward to integrate with host languages such as JavaScript and Python. There&amp;rsquo;s often an impedance mismatch between SQL&amp;rsquo;s declarative nature and the host language&amp;rsquo;s object-oriented or functional paradigms since SQL queries are typically written as strings within the host language, making it difficult to apply composable programming techniques.&lt;/p&gt;
&lt;p&gt;Several solutions have been developed over the years to address the impedance mismatch, with ORM (Object-Relational Mapping) libraries being one of them. ORMs such as Java&amp;rsquo;s Hibernate are widely used to model a relational database using objects, but there are &lt;a href=&#34;https://en.wikipedia.org/wiki/Object%E2%80%93relational_impedance_mismatch&#34;&gt;various difficulties&lt;/a&gt; in making them work well.&lt;/p&gt;
&lt;p&gt;Fortunately, there exists a category of libraries called query builders like &lt;a href=&#34;https://knexjs.org&#34;&gt;Knex&lt;/a&gt; for Node.js. Query builders allow the construction of queries programmatically without resorting to string concatenation and, unlike ORMs, they don&amp;rsquo;t suffer from the object-relational impedance mismatch.&lt;/p&gt;
&lt;h4 id=&#34;knex&#34;&gt;Knex&lt;/h4&gt;
&lt;p&gt;To demonstrate how a query builder works, let&amp;rsquo;s model a simple discussion forum. To keep things simple, we will use just three tables: &lt;strong&gt;post&lt;/strong&gt;, &lt;strong&gt;topic&lt;/strong&gt; and &lt;strong&gt;user&lt;/strong&gt;.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/user-post-topic-er-diagram.png&#34;  alt=&#34;The database tables for a simple discussion forum.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The database tables for a simple discussion forum.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;To retrieve all the posts in a topic along with information about the posters, we can use the following piece of code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;knex&amp;#39;&lt;/span&gt;)({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pg&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;connection&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;postgresql://localhost/my_forum&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getPostsInTopic&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;select&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.*&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.username&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.country&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;join&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.user_id&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;where&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.topic_id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;orderBy&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.id&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the result should look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1210&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;user_id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;327&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;topic_id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Hi there!&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;created_at&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-11-22T14:34:26.454Z&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;updated_at&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-11-22T14:34:26.454Z&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Asuka&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;country&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;JP&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Knex uses a &lt;a href=&#34;https://en.wikipedia.org/wiki/Fluent_interface&#34;&gt;fluent interface&lt;/a&gt; to build queries, making it possible to modify them by simply chaining additional methods. In the example below, we&amp;rsquo;ve made retrieving details about posters optional:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getPostsInTopic&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;withPosterDetails&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;myQuery&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	.&lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	.&lt;span style=&#34;color:#a6e22e&#34;&gt;select&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.*&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	.&lt;span style=&#34;color:#a6e22e&#34;&gt;where&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.topic_id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	.&lt;span style=&#34;color:#a6e22e&#34;&gt;orderBy&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.id&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;withPosterDetails&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;myQuery&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;myQuery&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	.&lt;span style=&#34;color:#a6e22e&#34;&gt;select&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.username&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.country&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	.&lt;span style=&#34;color:#a6e22e&#34;&gt;join&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.user_id&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;myQuery&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;pipelines&#34;&gt;Pipelines&lt;/h4&gt;
&lt;p&gt;Knex&amp;rsquo;s built-in object-oriented fluent interface works well, but for those who prefer a functional-style approach and the advantages that come with it, it&amp;rsquo;s possible to use an alternative interface based on pipelines.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.&amp;rdquo; &amp;ndash; Alan Perlis&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s surprisingly simple to implement a generic pipeline function that accepts multiple functions as input, passes an initial value to the first function, and executes the functions sequentially, passing the output of each function to the next:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pipe&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (...&lt;span style=&#34;color:#a6e22e&#34;&gt;functions&lt;/span&gt;) =&amp;gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;initialValue&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;functions&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;accumulator&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;accumulator&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;initialValue&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We will also need an identity function, which simply returns its input, acting similarly to a no-operation (no-op) function.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;identity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can now utilize the &lt;code&gt;pipe&lt;/code&gt; function to generate our SQL query using only function composition. The query modifier functions below return functions that accept the query object as their input:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;postTable&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;from&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;postsInTopic&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;) =&amp;gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.&lt;span style=&#34;color:#a6e22e&#34;&gt;select&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.*&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.&lt;span style=&#34;color:#a6e22e&#34;&gt;where&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.topic_id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;includePosterDetails&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.&lt;span style=&#34;color:#a6e22e&#34;&gt;select&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.username&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.country&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.&lt;span style=&#34;color:#a6e22e&#34;&gt;join&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.user_id&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;orderBy&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (...&lt;span style=&#34;color:#a6e22e&#34;&gt;fields&lt;/span&gt;) =&amp;gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;orderBy&lt;/span&gt;(...&lt;span style=&#34;color:#a6e22e&#34;&gt;fields&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getPostsInTopic&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;withPosterDetails&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;pipe&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;postsInTopic&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;withPosterDetails&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;includePosterDetails&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;identity&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;orderBy&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post.id&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )(&lt;span style=&#34;color:#a6e22e&#34;&gt;postTable&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;summary&#34;&gt;Summary&lt;/h4&gt;
&lt;p&gt;We have used a query builder to programmatically build SQL queries, and by using higher-order functions (functions that take other functions as arguments) and pipelines, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eliminated the need to pass the &lt;code&gt;query&lt;/code&gt; object explicitly.&lt;/li&gt;
&lt;li&gt;Made it easier to add, remove, or rearrange query modifiers without disrupting the overall structure.&lt;/li&gt;
&lt;li&gt;Improved maintainability and simplified testing since each query modifier function is a self-contained unit that can be reused across various queries and scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Discussed on:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=42265668&#34;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Self-documenting Code</title>
      <link>https://lackofimagination.org/2024/10/self-documenting-code/</link>
      <pubDate>Sun, 20 Oct 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/10/self-documenting-code/</guid>
      <description>&lt;p&gt;Think back to the last time you looked at an unfamiliar block of code. Did you immediately understand what it was doing? If not, you&amp;rsquo;re not alone &amp;ndash; many software developers, including myself, find it challenging to grasp unfamiliar code quickly.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at a simple JavaScript function that creates a user account:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;validateUserInput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;u105&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rules&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;/[a-z]{1,}/&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;/[A-Z]{1,}/&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;/[0-9]{1,}/&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;/\W{1,}/&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rules&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;every&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;rule&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rule&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;))) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;u212&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;u201&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At first glance, this function doesn&amp;rsquo;t look too bad apart from its use of cryptic error message codes. The argument &lt;code&gt;user&lt;/code&gt; is apparently an object that contains information about the user to be created. There are a few lines of code that checks if the password conforms to the password policy, using regular expressions. Then, there is a check to see if the user account already exists. Finally, if all the checks pass, the user&amp;rsquo;s password is hashed, and a function to create the new user is called; it probably returns something on success.&lt;/p&gt;
&lt;p&gt;One can certainly do a lot worse than this example, but there is also quite a bit of room for improvement.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;There are only two hard things in Computer Science: cache invalidation and naming things.&amp;rdquo; &amp;ndash; Phil Karlton&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Software developers frequently deal with abstract ideas and complex systems. Translating these abstractions into concrete, meaningful names that accurately reflect their behavior isn&amp;rsquo;t always straightforward. However, that isn&amp;rsquo;t really an excuse when we&amp;rsquo;re dealing with well-understood processes like user account creation, as in our example.&lt;/p&gt;
&lt;h4 id=&#34;named-constants-and-doing-one-thing-only&#34;&gt;Named Constants and Doing One Thing Only&lt;/h4&gt;
&lt;p&gt;The first change I would make is to use named constants instead of cryptic error codes. Also, putting complex logic, such as the password check, into its own function makes the code easier to read. After all, a function should ideally do one thing and one thing only. Implementing the password check separately allows it to be called from other functions as well.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;userValidationFailed&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;u105&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;userExists&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;u212&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;invalidPassword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;u201&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isPasswordValid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rules&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;/[a-z]{1,}/&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;/[A-Z]{1,}/&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;/[0-9]{1,}/&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;/\W{1,}/&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rules&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;every&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;rule&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rule&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After these changes, &lt;code&gt;createUser&lt;/code&gt; should look something like this. We can now immediately tell what would happen if a check fails.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;validateUserInput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;userValidationFailed&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;isPasswordValid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;userExists&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;invalidPassword&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;short-circuit-evaluation&#34;&gt;Short-circuit Evaluation&lt;/h4&gt;
&lt;p&gt;The revised code above is now easily readable, and we can leave it as is. However, in this case there&amp;rsquo;s an opportunity to make the code flow more linear by using short-circuit evaluation.&lt;/p&gt;
&lt;p&gt;Short-circuit evaluation allows us to simplify conditional statements by using logical operators. In this case, the &lt;strong&gt;||&lt;/strong&gt; operator checks the condition on the left, and if it&amp;rsquo;s false, it executes the function on the right.&lt;/p&gt;
&lt;p&gt;We have also flattened the password validation and existing user checks. The resulting code is shorter and has no nested logic.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throwError&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;validateUserInput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throwError&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;userValidationFailed&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;isPasswordValid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throwError&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;invalidPassword&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throwError&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;userExists&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;type-annotations&#34;&gt;Type Annotations&lt;/h4&gt;
&lt;p&gt;Self-documenting code involves writing code with minimal comments, but there is a category of comments that is useful not only for developers but also for compilers and IDEs, and that is annotations about the types of variables and arguments used.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not a fan of TypeScript, but I appreciate its ability to perform static type checks. Fortunately, there&amp;rsquo;s a way to add static type checking to JavaScript using only JSDoc comments. Alex Harri did an excellent job explaining how you can do that in &lt;a href=&#34;https://alexharri.com/blog/jsdoc-as-an-alternative-typescript-syntax&#34;&gt;this article&lt;/a&gt; if you&amp;rsquo;re interested.&lt;/p&gt;
&lt;p&gt;The following JSDoc comments make it clear what type of argument &lt;code&gt;createUser&lt;/code&gt; accepts and what it returns. The type definitions can be automatically picked up by the TypeScript compiler or an IDE like VS Code, giving you real-time feedback when you pass a value with the wrong type.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/** @typedef {{ id?: number, birthDate: Date, email: string, password: string }} User */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * Creates a user and returns the newly created user&amp;#39;s id on success
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @param {User} user
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; * @returns &amp;lt;Promise{any}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;validateUserInput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throwError&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;userValidationFailed&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;isPasswordValid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throwError&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;invalidPassword&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throwError&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;userExists&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;summary&#34;&gt;Summary&lt;/h4&gt;
&lt;p&gt;We have turned a seemingly simple but somewhat hard-to-follow function into a self-documenting function by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using named constants instead of cryptic error codes,&lt;/li&gt;
&lt;li&gt;Extracting complex logic and putting it in its own function,&lt;/li&gt;
&lt;li&gt;Using short-circuit evaluation to make the code flow linear,&lt;/li&gt;
&lt;li&gt;Introducing type annotations to help with static type checking and real-time coding feedback.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Discussed on:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=41924917&#34;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://programming.dev/post/20805887&#34;&gt;programming.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Avoiding if-else Hell: The Functional Style</title>
      <link>https://lackofimagination.org/2024/09/avoiding-if-else-hell-the-functional-style/</link>
      <pubDate>Mon, 23 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/09/avoiding-if-else-hell-the-functional-style/</guid>
      <description>&lt;p&gt;Many years ago, I took part in the development of a taxi-hailing mobile app that is still widely used today. I don&amp;rsquo;t know what kind of code they&amp;rsquo;re running now, but in those early days, the driver assignment code &amp;ndash;if I remember it correctly&amp;ndash; was &lt;em&gt;similar in spirit&lt;/em&gt; to the grossly simplified example shown below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignDriver&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculateDistances&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;vehicle&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferences&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Premium Driver&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isPremiumDriver&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are five levels of nested if statements in less than 30 lines of code. It doesn&amp;rsquo;t look so bad, some might say, but it&amp;rsquo;s not difficult to imagine how complicated this code can become with just a few more checks, such as surge pricing and loyalty programs, among other things.&lt;/p&gt;
&lt;p&gt;Fortunately, there are ways to flatten code, and you might be surprised by the end result as there will be no if statements left when we are finished refactoring it.&lt;/p&gt;
&lt;h4 id=&#34;early-returns-with-guard-clauses&#34;&gt;Early Returns with Guard Clauses&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s start with the easy pickings. The very first if statement, which checks the maximum allowed distance between a driver and the rider, can be converted into a guard clause to remove one level of nesting since it applies to all cases. We can similarly add one more guard clause for the preferred vehicle check to remove an additional level of nesting. The resulting code is shown below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignDriver&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculateDistances&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;vehicle&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		    &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferences&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Premium Driver&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isPremiumDriver&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		            &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		        } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		        &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		        &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		    &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		    &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;decision-tables&#34;&gt;Decision Tables&lt;/h4&gt;
&lt;p&gt;Instead of hardcoding the logic inside our function, we can place each if-else block in its own function and put those functions in an array, forming a decision table. Then, we run each function in the decision table until we get a positive response. Obviously, the entries in the table must be sorted from most specific to least specific for it to function properly in our case.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;conditions&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferences&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Premium Driver&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isPremiumDriver&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferences&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Premium Driver&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignDriver&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;conditions&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculateDistances&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;vehicle&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;conditions&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;condition&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;condition&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;))) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With the addition of a decision table, we have completely eliminated nested ifs, and as an added bonus, the driver assignment logic can now be changed by simply editing the conditions array.&lt;/p&gt;
&lt;h4 id=&#34;function-composition&#34;&gt;Function Composition&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s get rid of the remaining if statements by converting the for loop to &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find&#34;&gt;Array.find()&lt;/a&gt; and creating individual functions for each if statement inside the loop:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;conditions&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferences&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Premium Driver&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isPremiumDriver&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferences&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Premium Driver&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rating&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.5&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignDriver&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;conditions&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculateDistances&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isDriverClose&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driverDistances&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isVehicleOK&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preferredVehicle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;vehicle&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isAConditionSatisfied&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;conditions&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;condition&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;condition&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;availableDrivers&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isDriverClose&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isVehicleOK&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isAConditionSatisfied&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rider&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;assignedDriver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;summary&#34;&gt;Summary&lt;/h4&gt;
&lt;p&gt;By utilizing basic functional programming principles, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removed all nested if statements,&lt;/li&gt;
&lt;li&gt;Decoupled most of the logic and made it easily modifiable,&lt;/li&gt;
&lt;li&gt;Made the code easier to follow by placing individual if-else blocks in their own functions,&lt;/li&gt;
&lt;li&gt;Finally, as a nice side effect, cut the program size by one-third.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Discussed on:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://programming.dev/post/19794204&#34;&gt;programming.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/programming/comments/1fozl65/avoiding_ifelse_hell_the_functional_style/&#34;&gt;Reddit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Firewalling Your Code</title>
      <link>https://lackofimagination.org/2024/08/firewalling-your-code/</link>
      <pubDate>Tue, 20 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/08/firewalling-your-code/</guid>
      <description>&lt;p&gt;When writing code, you can call any function as long as it&amp;rsquo;s public, and similarly, you can access any object&amp;rsquo;s public properties or methods. Usually, access to code is all or none &amp;ndash; a piece of code can be either public or private. Access to private code is typically controlled by the upper layer that encapsulates it. Although some languages support semi-private (also known as protected) object members, that&amp;rsquo;s a merely variation on the public/private theme.&lt;/p&gt;
&lt;p&gt;Lately, I&amp;rsquo;ve been thinking about ways to implement more fine-grained access controls and have looked to the networking world for inspiration. In computer networks, firewalls are used to restrict access to network resources. For example, you can set firewall rules to allow only the application and analytics servers to access the database server &amp;ndash; or more specifically, the port on which the database service is running. There&amp;rsquo;s no need for encapsulation, inheritance or interfaces to implement; you simply define access rules.&lt;/p&gt;
&lt;p&gt;Unlike network firewalls, when accessing a public property or method in a program, we don&amp;rsquo;t normally check whether the caller has the right to do so. Since the callee is public, any part of the program should be able to call it without any restrictions. However, I believe there are cases when more fine-grained controls could be useful.&lt;/p&gt;
&lt;p&gt;In the commonly used &lt;a href=&#34;https://lackofimagination.org/2024/03/software-architectures-in-a-nutshell/&#34;&gt;multi-tier architecture&lt;/a&gt;, code flows in one direction only &amp;ndash; from the upper-most layer to the layers below. Modules can call the other modules in the same layer. They can also call modules in the layer directly below, but never above.&lt;/p&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 544 217&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 192,0 L 224,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,0 L 320,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,0 L 416,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,16 L 224,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 320,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,16 L 416,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,48 L 224,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,48 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,48 L 416,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 144,80 L 168,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,80 L 224,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 320,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,80 L 416,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,96 L 264,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,96 L 360,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 152,112 L 176,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,112 L 224,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,112 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,112 L 416,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,144 L 224,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,144 L 320,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,144 L 416,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,176 L 224,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,176 L 320,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,176 L 416,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,192 L 224,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,192 L 320,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,192 L 416,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,16 L 176,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,112 L 176,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,16 L 192,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,80 L 192,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,144 L 192,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,16 L 224,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,80 L 224,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,144 L 224,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,16 L 240,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,96 L 240,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,16 L 272,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 288,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 288,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,144 L 288,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,16 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,80 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,144 L 320,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,16 L 336,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,96 L 336,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 368,16 L 368,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,16 L 384,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,80 L 384,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,144 L 384,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,16 L 416,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,80 L 416,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,144 L 416,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 432,16 L 432,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;160.000000,112.000000 148.000000,106.400002 148.000000,117.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 152.000000, 112.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;176.000000,80.000000 164.000000,74.400002 164.000000,85.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 168.000000, 80.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;272.000000,96.000000 260.000000,90.400002 260.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 264.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;368.000000,96.000000 356.000000,90.400002 356.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 360.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 192,0 A 16,16 0 0,0 176,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,0 A 16,16 0 0,1 240,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,0 A 16,16 0 0,0 272,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,0 A 16,16 0 0,1 336,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,0 A 16,16 0 0,0 368,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,0 A 16,16 0 0,1 432,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,176 A 16,16 0 0,0 192,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,176 A 16,16 0 0,1 224,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,176 A 16,16 0 0,0 288,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,176 A 16,16 0 0,1 320,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 368,176 A 16,16 0 0,0 384,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 432,176 A 16,16 0 0,1 416,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;O&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
&lt;p&gt;How can you ensure that the modules follow these rules? Unless you&amp;rsquo;re conducting in-depth code reviews, it isn&amp;rsquo;t too difficult to end up with haphazard code paths that might have maintenance and security implications.&lt;/p&gt;
&lt;p&gt;As a proof of concept, I&amp;rsquo;ve created a Node.js library called &lt;a href=&#34;https://www.npmjs.com/package/firewall-js&#34;&gt;firewall-js&lt;/a&gt; using JavaScript proxies. It relies on the filesystem structure of a code base to limit access to certain parts.&lt;/p&gt;
&lt;p&gt;Take a simple backend application with three layers: &lt;strong&gt;routes &amp;gt; controllers &amp;gt; services&lt;/strong&gt;. Each layer has its own directory, and each file in a directory houses a module. The directory listing should look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; controllers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; routes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;v services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   auth.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   log.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   user.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, if you want all the controller modules (upper layer) and all other service modules (same layer) to have access to a particular service module, we can do it with a single line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// services/user.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firewall&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;firewall-js&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bcrypt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;hash&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;where&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;head&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;exports&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firewall&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;allow&lt;/span&gt;([&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;controllers&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;services&amp;#39;&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you attempt to call, for example, &lt;em&gt;userService.hashPassword()&lt;/em&gt; from a file in any other directory, an exception will be thrown:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error: Access denied for hashPassword from /Users/me/my-app/routes/main.js:51:19
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can go one step further, and allow access not just from directories, but from files too. In the following example, only the userProfile controller can access userService, and no one else:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;exports&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firewall&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;allow&lt;/span&gt;([&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;controllers/userProfile.js&amp;#39;&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ve deliberately made the filesystem structure the basis of the access control system. This, I believe, has two benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A clear-cut organization of code with directories acting as layers and files as modules within those layers.&lt;/li&gt;
&lt;li&gt;Permissions that are easy to understand, since most everyone is familiar with how a filesystem works.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An access control system like the one implemented here can help prevent maintenance issues, such as dependencies or coupling between unrelated parts of the application, and enhance security by limiting the potential for unauthorized actions. Additionally, it doesn&amp;rsquo;t require any drastic changes to the existing code base, as long as a sensible directory hierarchy is in place. Firewalling a module can then be as easy as adding a single line of code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href=&#34;https://github.com/aycangulez/firewall-js&#34;&gt;firewall-js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discussed on:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=41338240&#34;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Building Valeria: A Compact Horn-Loaded Speaker</title>
      <link>https://lackofimagination.org/2024/07/building-valeria-a-compact-horn-loaded-speaker/</link>
      <pubDate>Sat, 27 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/07/building-valeria-a-compact-horn-loaded-speaker/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been using a pair of KEF LS50 Metas as my front speakers and my &lt;a href=&#34;https://lackofimagination.org/2023/11/making-a-kef-ls50-clone/&#34;&gt;DIY KEF LS50 clone&lt;/a&gt; as the center channel speaker in my home theater for a while now. I&amp;rsquo;ve been generally happy with the smooth sound they produce, but there&amp;rsquo;s one thing lacking: dynamics.&lt;/p&gt;
&lt;p&gt;Granted, these are small bookshelf speakers, but they receive assistance from my DIY subwoofers, so they don&amp;rsquo;t have to reproduce the most difficult frequencies of all: sub-bass. Unfortunately, at my listening distance of approximately 3.5 meters (~11.5 feet), they start to distort noticeably at higher volumes, which reduces the impact of certain movies, especially ones with epic soundtracks.&lt;/p&gt;
&lt;p&gt;Lately, I&amp;rsquo;ve been thinking about building high-sensitivity speakers using pro-audio drivers. Horn-loaded compression drivers, which are used to reproduce high frequencies in the pro-audio world, can get really loud with little distortion. A well-designed horn can also achieve constant directivity &amp;ndash; the horizontal and vertical dispersion of high frequencies are consistent and limited to specific angles (e.g., nominally ±45 degrees horizontal and ±30 degrees vertical).&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/bc_me10_horn_horizontal_directivity.png&#34;  alt=&#34;The horizontal directivity plot of a constant directivity horn as measured by mtg-designs.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The horizontal directivity plot of a constant directivity horn as measured by mtg-designs.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Some hi-fi speakers that use waveguides can also control high-frequency directivity similarly to horns, but the waveguides they employ are usually not deep enough to boost the efficiency of the dome tweeters as regular horns do.&lt;/p&gt;
&lt;p&gt;Coming from coaxial speakers, where the tweeter is located at the center of the mid-bass driver like the KEFs, my main goal, apart from improved dynamics, is to achieve decent vertical directivity.&lt;/p&gt;
&lt;p&gt;With non-coaxial speakers, you need to position the mid-bass driver as close as possible to the tweeter to minimize driver center-to-center spacing. However, this is often not sufficient because you also need to use a lower crossover frequency than usual. Unfortunately, many tweeters will self-destruct if the crossover frequency is too low and the volume is too high.&lt;/p&gt;
&lt;p&gt;With pro-audio compression drivers mounted on a horn, you can easily get them to play anywhere from half to a full octave below the lower limit of a typical dome tweeter (e.g., 1.6 kHz instead of 2.2 kHz). This helps with vertical directivity as wavelengths get longer the lower you go in frequency, resulting in less destructive interference between the drivers.&lt;/p&gt;
&lt;p&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/building-valeria-speaker/qsc_k12_2.jpg); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/qsc_k12_2.jpg&#34; alt=&#34;A pro audio speaker.&#34; /&gt;

A typical pro-audio speaker, often seen at events like weddings and live music venues, usually features a large mid-bass driver (12&amp;quot; and 15&amp;quot; are common) and a large horn. However, as you might guess, this results in high driver center-to-center spacing. Fortunately, you don&amp;rsquo;t need concert-level output in a typical home environment, especially when one is sitting within a few meters of the speakers. You want it loud, but not deafeningly so, if you value your hearing.&lt;/p&gt;
&lt;p&gt;After doing some research, I came across small horns like the 13 cm (~5&amp;quot;) B&amp;amp;C ME10 and a relatively high output 6.5&amp;quot; mid-bass driver: Lavoce WSF061.52. By using physically small components, a driver center-to-center spacing of 16 cm (~6.3&amp;quot;) that corresponds to about 3/4 wavelength distance at the crossover frequency turned out to be possible. You can reduce the distance even more if you&amp;rsquo;re willing to mount the mid-bass driver from the inside, but 16 cm is good enough for me.&lt;/p&gt;
&lt;p&gt;The only problem left is that the mid-bass driver&amp;rsquo;s frequency response takes a nosedive starting around 120 Hz, even after adding a port. No problem, though &amp;ndash; I have multiple subwoofers with dual 12&amp;quot; drivers for low-bass duty. Let&amp;rsquo;s get to work!&lt;/p&gt;
&lt;p&gt;I designed a compact cabinet with internal bracing that touches the drivers, making the drivers part of the brace and reinforcing the front and rear sides of the cabinet.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/cabinet_cad_drawing_with_dimensions.png&#34;  alt=&#34;Cabinet drawing with dimensions.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Cabinet drawing with dimensions.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I cut the pieces on my &lt;a href=&#34;https://lackofimagination.org/2023/09/cnc-router-control-box-wiring/&#34;&gt;CNC router&lt;/a&gt; using 18 mm (~3/4&amp;quot;) MDF and then added 45-degree bevels to all sides on my router table. I also added a 6 mm MDF backing to the front baffle to make it stronger.&lt;/p&gt;
&lt;p&gt;The pieces are simple enough that they can be cut with a circular saw or jigsaw, and the holes can be cut with a handheld router. The bevels are optional &amp;ndash; one can also use regular butt joints after resizing the pieces, of course. I glued the pieces together using wood glue, aligning them with painter&amp;rsquo;s tape. Band clamps really came in handy for gluing.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/parts_ready_for_dry_fit.jpg&#34;  alt=&#34;Parts ready for dry fit&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Parts ready for dry fit&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Borrowing an idea from KEF, just like in my &lt;a href=&#34;https://lackofimagination.org/2023/11/making-a-kef-ls50-clone/&#34;&gt;KEF LS50 clone build&lt;/a&gt;, I attached the braces to the walls with 3M VHB tape instead of regular wood glue. The soft layer in between acts as constrained-layer damping to reduce resonances.&lt;/p&gt;
&lt;p&gt;I glued the port, made out of a plastic water pipe (5.7 cm inner diameter), to the back wall with hot glue. The port is quite short (only 3 cm, not counting the outside MDF flare). The crossover board is installed on the side wall with standoffs. I placed M4 threaded inserts into the front baffle so that I could remove and install the drivers as many times as needed without having to worry about stripping the holes, which is all too easy with MDF.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/crossover_mounted_inside_cabinet.jpg&#34;  alt=&#34;Braces installed, drivers and crossover board mounted.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Braces installed, drivers and crossover board mounted.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I was originally going to use an active crossover, but after doing a few simulations in &lt;a href=&#34;https://kimmosaunisto.net&#34;&gt;VituixCAD&lt;/a&gt;, I noticed that a simple passive crossover with only five parts would work nicely. With active crossovers, you can get the frequency response ruler-flat, but you also need two channels of amplification and DSP &amp;ndash;one for the mid-bass and one for the compression driver&amp;ndash; whereas a passive crossover can make do with only one amplifier channel and no DSP. Additionally, active crossovers can have more background noise than passive crossovers because the amplifiers are connected directly to the drivers.&lt;/p&gt;
&lt;p&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/building-valeria-speaker/crossover_circuit_diagram.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/crossover_circuit_diagram.png&#34; alt=&#34;Crossover circuit diagram&#34; /&gt;

I used a second order low-pass (a 2.5 mH laminated silicon steel core inductor in series and a 15 µF capacitor in parallel) for the mid-bass driver. For the compression driver, I used a first order high-pass (a 3.3 µF capacitor in series) and an L-pad circuit (two 4.7 ohm resistors in series/parallel) to reduce its level and flatten the response. You wouldn&amp;rsquo;t normally use a first order high-pass with compression drivers, but once again we are going to use it in a home environment where it will unlikely to be stressed, and besides, the electrical roll-off starts quite early. Finally, the compression driver is connected in reverse polarity to avoid a dip in the frequency response at the crossover frequency.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/crossover_close_up.jpg&#34;  alt=&#34;Crossover board close-up view.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Crossover board close-up view.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I patched the rough spots on the cabinet with wood filler, sanded it smooth, and applied 19 mm (3/4&amp;quot;) roundovers to all sides on my router table. Then I took the speaker outside and painted it with several coats of MDF primer using a spray gun. Once the primer had dried, I sprayed several coats of top coat (steel blue). Since I was using water-based paints, I added some paint conditioner (pouring medium) to help reduce orange peel.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/cabinet_painted.jpg&#34;  alt=&#34;Cabinet Painted&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Cabinet Painted&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Once the paint had dried, it was time to add absorption material to deal with the standing waves that would form inside the cabinet. I lined the walls with felt (5 to 10 mm thick) and placed extra-thick 60 mm pyramid foam on top, leaving enough space for the drivers, of course.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/absorption_materials_placed.jpg&#34;  alt=&#34;Absorption materials placed in cabinet&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Absorption materials placed in cabinet&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Here&amp;rsquo;s the side view of the cabinet with drivers waiting to be mounted. The mid-bass driver, Lavoce WSF061.52, has a 1.5&amp;quot; voice coil and a paper cone, while the compression driver, Lavoce DF10.142LM, has a 1.4&amp;quot; voice coil and a polyester (likely Mylar) diaphragm.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/cabinet_and_drivers_from_the_side.jpg&#34;  alt=&#34;Cabinet side-view with drivers to be installed.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Cabinet side-view with drivers to be installed.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Finally, Valeria is complete standing next to one of my KEF LS50 Metas. They&amp;rsquo;re roughly similar in size. Valeria&amp;rsquo;s dimensions are 19 x 33 x 22.5 cm (WxHxD), while the KEF LS50 Meta is 20 x 30 x 28 cm (WxHxD).&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/next_to_kef_ls50_meta.jpg&#34;  alt=&#34;Valeria next to a KEF LS50 Meta&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Valeria next to a KEF LS50 Meta&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;So, how does it sound? By itself, the bass is anemic, as expected. However, as soon as you add a subwoofer to the mix, this little speaker transforms into something special. Female vocals are particularly lifelike, thanks to the effortless dynamics and silky smooth sound of the compression driver. With tracks like &amp;ldquo;Can&amp;rsquo;t Catch Me Now&amp;rdquo; by Olivia Rodrigo, I found myself turning up the volume higher and higher, yet the sound remained clean throughout.&lt;/p&gt;
&lt;p&gt;Valeria really shines at high volumes. In fact, on some bass-heavy tracks, I discovered bass resonances in my room that I hadn&amp;rsquo;t noticed before. With the LS50 Metas, I couldn&amp;rsquo;t turn the volume up that high without seriously distorting the sound.&lt;/p&gt;
&lt;p&gt;Now, some measurements&amp;hellip; The average sensitivity (300 Hz-3,000 Hz) is approximately 92 dB/2.83V/1m, measured with a UMIK-1 microphone. This means that only 1 to 2 Watts of amplifier power is needed to achieve very loud volumes at 1 meter from the speaker.&lt;/p&gt;
&lt;p&gt;The impedance plot looks relatively clean, with the exception of a resonance at around 950 Hz. How audible this is, it&amp;rsquo;s hard to say. The nominal impedance is 8 ohms, and the worst-case EPDR (Equivalent Peak Dissipation Resistance) is around 3 ohms at 200 Hz, making Valeria a relatively easy load for the partnering amplifier, as John Atkinson would say.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/impedance_and_phase_plot.png&#34;  alt=&#34;Impedance and phase plot.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Impedance and phase plot.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The frequency response is mostly within ±3 dB from 120 to 20,000 Hz (quasi-anechoic measurement). The mid-bass to compression driver crossover frequency is 1.6 kHz. As for the subwoofer crossover frequency, 120 Hz looks like a good starting point. You can probably go a bit lower if you place the speaker close to walls, as this will result in some bass boost from nearby boundaries. It’s also a good idea to run the subwoofer(s) a few dB hot for optimal integration.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/quasi_anechoic_frequency_response_corrected.png&#34;  alt=&#34;On-axis quasi-anechoic frequency response.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;On-axis quasi-anechoic frequency response.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The horizontal directivity is ±45 degrees nominal, but starts to narrow gradually above 3.5 kHz. Thanks to the smooth directivity, the frequency response can be equalized without any ill effects.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/valeria_horizontal_directivity.png&#34;  alt=&#34;Smooth horizontal directivity (normalized).&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Smooth horizontal directivity (normalized).&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;It&amp;rsquo;s surprising how a small speaker like this one can get so loud yet manage to stay so clean. The trade-off, of course, is little to no low-bass output by itself, but that&amp;rsquo;s precisely what subwoofers are for.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Update (August 3, 2024):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reddit.com/user/DZCreeper/&#34;&gt;DZCreeper&lt;/a&gt; over on Reddit suggested adding a foam plug to the throat of the horn and pointed me to an expired &lt;a href=&#34;https://patents.google.com/patent/US7708112B2/&#34;&gt;patent&lt;/a&gt; by Earl Geddes, who&amp;rsquo;s well known for his extensive research on waveguides.&lt;/p&gt;
&lt;p&gt;The idea is simple: Placing some open-cell foam inside the horn will absorb internal reflections, leading to cleaner sound. The downside is the loss of 2 to 3 dB of high-frequency output, but since the crossover is already running the compression driver a few dB hot relative to the mid-bass driver, this shouldn&amp;rsquo;t pose a problem.&lt;/p&gt;
&lt;p&gt;I got some 30 pores-per-inch (ppi) open-cell foam from a pet store, typically used for things like aquarium filters. I then cut the foam with scissors, and placed it loosely inside the horn throat. It&amp;rsquo;s important not to pack it tightly, otherwise you will lose too much output above 10 kHz.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/horn_foam_plug.jpg&#34;  alt=&#34;Horn phase plug made of open-cell foam.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Horn phase plug made of open-cell foam.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;With the foam plug in place, I was expecting consistent attenuation in the frequency range that the compression driver is producing, but I was pleasantly surprised to see the response immediately above the crossover frequency become smoother, not just lower in volume. It appears that the foam plug actually helped with the cancellation caused by the phase mismatch between the drivers over an octave above the crossover frequency.&lt;/p&gt;
&lt;p&gt;As for any improvement in sound quality beyond the frequency response change, I can&amp;rsquo;t really tell. Geddes has written on several online forums that the effect would be subtle and not easy to measure, especially in well designed horns.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/quasi_anechoic_frequency_response_with_foam_plug_corrected.png&#34;  alt=&#34;Frequency response after foam plug inserted.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Frequency response after foam plug inserted.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;With the foam plug, another pleasant surprise is the improved horizontal directivity, which has become more balanced.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/valeria_horizontal_directivity_with_foam_plug.png&#34;  alt=&#34;Improved horizontal directivity with foam plug.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Improved horizontal directivity with foam plug.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/building-valeria-speaker/alternative_crossover_diagram_by_dzcreeper.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/building-valeria-speaker/alternative_crossover_diagram_by_dzcreeper.png&#34; alt=&#34;Alternative crossover diagram&#34; /&gt;

&lt;strong&gt;Alternative Crossover:&lt;/strong&gt;&lt;br&gt;
Finally, &lt;a href=&#34;https://www.reddit.com/user/DZCreeper/&#34;&gt;DZCreeper&lt;/a&gt; has graciously designed an alternative third order crossover for Valeria. It has a few more parts, but offers a relatively flatter response.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Update (Dec 2, 2024):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The frequency response graphs and the sensitivity figure have been updated to account for the speaker baffle effect in near-field measurements.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Teaching Programming with BASIC</title>
      <link>https://lackofimagination.org/2024/07/teaching-programming-with-basic/</link>
      <pubDate>Sun, 14 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/07/teaching-programming-with-basic/</guid>
      <description>&lt;p&gt;I started programming in GW-BASIC on an &lt;a href=&#34;https://en.wikipedia.org/wiki/IBM_PC_compatible&#34;&gt;IBM PC clone&lt;/a&gt; running MS-DOS. Back then, many so-called home and business computers came bundled with a BASIC interpreter, mostly made by or licensed from Microsoft.&lt;/p&gt;
&lt;p&gt;And they all looked similar. You were greeted by a screen with a READY or OK prompt and a blinking cursor waiting for your input. The &amp;ldquo;screen editor&amp;rdquo; and interpreter were all in one in the true sense of the word &amp;ndash; they weren&amp;rsquo;t bolted together like the separate text editors and interpreters/compilers we use these days. You wrote your programs &lt;em&gt;inside&lt;/em&gt; the BASIC interpreter, which also doubled as a line-oriented text editor.&lt;/p&gt;
&lt;p&gt;To change a line, you first listed the approximate range of lines you were interested in with the LIST command. For example, &lt;code&gt;LIST 100-200&lt;/code&gt; would list all the lines starting with 100 and ending with 200, assuming they fit on the typical 25-line screens of the day. There was no scrolling, so if the line you wanted to change wasn&amp;rsquo;t visible, you had to specify a smaller range. Then, you would go to the line with the cursor keys, make your changes, and, &lt;em&gt;this is very important&lt;/em&gt;, press the Enter key to save the new line. After that, you would go to an empty line and enter &lt;code&gt;RUN&lt;/code&gt; to run your program.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a simple BASIC program, which prints out the squares of integers from 1 to N as specified by the user:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;10 INPUT &amp;#34;How many numbers to square? Enter 0 to exit. &amp;#34;, N
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;20 IF N = 0 THEN END
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;30 FOR I = 1 TO N
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;40   PRINT I ^ 2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;50 NEXT I
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;60 GOTO 10
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The line numbers are typically incremented by 10 to allow for the addition of more lines in between at a later time. Thankfully, GW-BASIC had a useful &lt;code&gt;RENUM&lt;/code&gt; command that would automatically renumber some or all of the lines in your program and update any referenced lines. You can think of it as an early example of automatic refactoring.&lt;/p&gt;
&lt;p&gt;Enough with the peculiarities of GW-BASIC, though. Let&amp;rsquo;s go back to our BASIC program. I&amp;rsquo;ve written code in quite a few languages over some three decades, and I believe the BASIC program above is probably as short yet thorough as you can get in demonstrating how computers &lt;em&gt;actually&lt;/em&gt; work to absolute beginners.&lt;/p&gt;
&lt;p&gt;In just 6 lines, you get user input, store it in a variable, compare the variable with a value, run an operation inside a loop, and finally jump back to the beginning. There are no functions, no object oriented programming, no libraries, no frameworks &amp;ndash; just a few simple instructions and jumps that can be directly mapped to machine code.&lt;/p&gt;
&lt;p&gt;Once the beginner grasps the building blocks of programming &amp;ndash;variables, conditionals, loops, and branching&amp;ndash; you can start showing them how to reuse code. BASIC doesn&amp;rsquo;t have local variables or real functions, but it does have subroutines, which can be utilized with the &lt;code&gt;GOSUB&lt;/code&gt; statement.&lt;/p&gt;
&lt;p&gt;The following example program calls the text mode window drawing subroutine starting at line 1000 with 7 variables set: X, Y, W (Width), H (Height), FG (Foreground color), BG (Background color), TITLE$ (Title text of the window). The subroutine draws windows by using the &lt;a href=&#34;https://en.wikipedia.org/wiki/Code_page_437&#34;&gt;ASCII codes of the line drawing characters&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;5 COLOR 7,0: CLS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;10 X=10: Y=5: W=30: H=8: FG=15: BG=1: TITLE$=&amp;#34;Info&amp;#34;: GOSUB 1000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;20 X=20: Y=8: W=40: H=10: FG=15: BG=4: TITLE$=&amp;#34;Warning&amp;#34;: GOSUB 1000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;30 COLOR 7,0: END
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1000 &amp;#39;Draws a window with title bar
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1005 COLOR FG,BG
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1010 LOCATE Y,X: PRINT CHR$(213) &amp;#39;Top left
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1020 FOR I=1 TO W-2: LOCATE Y,X+I: PRINT CHR$(205): NEXT I &amp;#39;Top
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1030 LOCATE Y,X+W-1: PRINT CHR$(184) &amp;#39;Top right
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1040 FOR I=1 TO H-2: LOCATE Y+I,X: PRINT CHR$(179): NEXT I &amp;#39;Left
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1050 FOR I=1 TO H-2: LOCATE Y+I,X+W-1: PRINT CHR$(179): NEXT I &amp;#39;Right
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1060 LOCATE Y+H-1,X: PRINT CHR$(192) &amp;#39;Bottom Left
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1070 FOR I=1 TO W-2: LOCATE Y+H-1,X+I: PRINT CHR$(196): NEXT I &amp;#39;Bottom
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1080 LOCATE Y+H-1,X+W-1: PRINT CHR$(217) &amp;#39;Bottom right
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1085 FOR I=1 TO H-2: FOR J=1 TO W-2: LOCATE Y+I,X+J: PRINT &amp;#34; &amp;#34;: NEXT J,I &amp;#39;Inside
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1090 LOCATE Y,X+(W-LEN(TITLE$))/2-1: PRINT &amp;#34; &amp;#34;TITLE$&amp;#34; &amp;#34; &amp;#39;Title bar
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1100 RETURN
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We just drew our own windows from top to bottom, complete with borders, using only 17 lines of code. Once again, we didn&amp;rsquo;t rely on any libraries, and just used the built-in BASIC statements. Here is the output of the program:&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/two_windows_gwbasic.png&#34;  alt=&#34;Two text-mode windows drawn on screen by a GW-BASIC subroutine.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Two text-mode windows drawn on screen by a GW-BASIC subroutine.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration.&amp;rdquo; &amp;ndash;
Edsger Dijkstra&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, obviously I don&amp;rsquo;t agree with Dijkstra here. Yes, BASIC lacks some universally agreed-upon programming concepts, and it does encourage the use of GOTO, which can easily lead to spaghetti code. However, at the lowest level, computer machine code doesn&amp;rsquo;t employ any of those concepts either. It&amp;rsquo;s just a bunch of CPU instructions and lots of jumping around.&lt;/p&gt;
&lt;p&gt;If one wants to understand how computers work, I think it&amp;rsquo;s easier to start closer to the level they operate, and introduce useful abstractions like functional and object oriented programming as time goes on. Machine code and Assembly are too low level for beginners and even for veteran programmers. Popular programming languages like JavaScript and Python, on the other hand, are too high level &amp;ndash; they&amp;rsquo;re far removed from the underlying hardware.&lt;/p&gt;
&lt;p&gt;For me, BASIC feels about right as the first programming language. However, as soon as you start to feel its limitations, you should graduate to a modern language. I&amp;rsquo;m sure you will appreciate modern languages more once you&amp;rsquo;ve spent some time in BASIC, but you might also find that some modern languages and frameworks are more complicated than necessary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Worth a look:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://robhagemans.github.io/pcbasic/&#34;&gt;PC-BASIC&lt;/a&gt;, a cross-platform interpreter for GW-BASIC&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=JDnypVoQcPw&#34;&gt;Writing a Tetris clone in GW-BASIC&lt;/a&gt; demonstrates advanced GW-BASIC features&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Discussed on:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://hackaday.com/2024/07/21/ask-hackaday-should-we-teach-basic/&#34;&gt;Hackaday&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=41017172&#34;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://programming.dev/post/16880110&#34;&gt;programming.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>I Don&#39;t Trust My Own Code</title>
      <link>https://lackofimagination.org/2024/05/i-dont-trust-my-own-code/</link>
      <pubDate>Mon, 27 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/05/i-dont-trust-my-own-code/</guid>
      <description>&lt;p&gt;Recently, I had a conversation with a junior developer on my team. Let&amp;rsquo;s call him Alan. We were talking about a new notification feature that was going to be used to send reminder e-mails to potentially thousands of people if they had forgotten to enter certain data in the last month or so.&lt;/p&gt;
&lt;p&gt;Alan was confident that the code he had written was correct. &amp;ldquo;I&amp;rsquo;ve tested it well,&amp;rdquo; he said. I had heard some variation of this statement from so many developers over the years that I sighed: &amp;ldquo;I&amp;rsquo;ve been writing code in a professional capacity for over two decades now, and yet even I don&amp;rsquo;t trust the code I&amp;rsquo;ve written.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;You can probably guess what happened to Alan&amp;rsquo;s &amp;ldquo;well-tested&amp;rdquo; code. In testing, a number of bugs were discovered, many of which were caused by incorrect assumptions on his part.&lt;/p&gt;
&lt;p&gt;Now, I&amp;rsquo;m not picking on Alan. In fact, I hired him and the rest of the team for that matter. He may be short on experience and a little overconfident, but generally writes decent code, and constantly tries to improve himself, a trait that is more important than experience in my book.&lt;/p&gt;
&lt;p&gt;As for me, I&amp;rsquo;m certainly not &lt;a href=&#34;https://en.wikipedia.org/wiki/John_Carmack&#34;&gt;one of the best software developers&lt;/a&gt; in the world. I may not be a great one either. I&amp;rsquo;ve seen quite a few developers who have written qualitatively worse code than I have, and relatively few who have written better code. So, statistically speaking, I consider myself above average. How much above average, it&amp;rsquo;s hard to say, but it doesn&amp;rsquo;t really matter as long as one is aware of their limitations.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague.&amp;rdquo; &amp;ndash; Edsger Dijkstra&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh yes, clever tricks. It&amp;rsquo;s tempting to use them from time to time, but are you going to remember how you solved a problem when you revisit your code six months later? If you need to go through your code line by line just to understand what it&amp;rsquo;s doing, another developer reading it is unlikely to fare better.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s often said that writing code is easier than reading it, but that doesn&amp;rsquo;t have to be the case. When writing long sections of code, I find it helpful to stop every once in a while and review the code as if I were someone seeing it for the first time. I ask myself a few basic questions: Are my functions short? Do they have descriptive names? Is complex logic encapsulated within their own functions? Am I passing too many arguments? And so on.&lt;/p&gt;
&lt;p&gt;Assuming that one writes reasonably easy-to-understand code, a major source of errors typically stems from the assumptions made by developers. Reality is messy. There are almost always edge cases that will bite you in the back.&lt;/p&gt;
&lt;p&gt;Numerous &amp;ldquo;falsehoods programmers believe about X&amp;rdquo; pages on the web list commonly held but incorrect beliefs. For brevity, I will only include some I had the displeasure to deal with:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Falsehoods About Time:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A timestamp represents the time that an event actually occurred.&lt;/li&gt;
&lt;li&gt;The time zone in which a program has to run will never change.&lt;/li&gt;
&lt;li&gt;Daylight saving time happens at the same time every year.&lt;/li&gt;
&lt;li&gt;Weeks start on Monday.&lt;/li&gt;
&lt;li&gt;Time always moves forwards.&lt;/li&gt;
&lt;li&gt;My software stack will handle it without me needing to do anything special.&lt;br&gt;
[&lt;a href=&#34;https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca&#34;&gt;More&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Falsehoods About Text:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASCII data represents text.&lt;/li&gt;
&lt;li&gt;Interpreting common ASCII data such as tabs, carriage returns, and line-feeds is defined.&lt;/li&gt;
&lt;li&gt;A Unicode file that only contains ASCII letters, encoded in UTF-8, is a valid ASCII file.&lt;/li&gt;
&lt;li&gt;Latin-1 can be mapped directly into Unicode.&lt;br&gt;
[&lt;a href=&#34;https://wiesmann.codiferes.net/wordpress/archives/30296&#34;&gt;More&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Falsehoods About Prices:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prices can&amp;rsquo;t have more precision than the smallest sub-unit of the currency.&lt;/li&gt;
&lt;li&gt;You can always use a dot (or a comma, etc.) as a decimal separator.&lt;/li&gt;
&lt;li&gt;Given two currencies, there is only one exchange rate between them at any given point in time.&lt;br&gt;
[&lt;a href=&#34;https://gist.github.com/rgs/6509585&#34;&gt;More&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, for a curated list of a great many falsehoods, &lt;a href=&#34;https://github.com/kdeldycke/awesome-falsehood&#34;&gt;try this&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Only the paranoid survive.&amp;rdquo; &amp;ndash; Andy Grove&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A piece of advice, if I may: Assume nothing. Don&amp;rsquo;t trust anything including your own code. List your assumptions. Double, triple-check every single one of them. If some of your assumptions are wrong, it doesn&amp;rsquo;t matter if your code is correct &amp;ndash; it will inevitably fail. Challenge your assumptions, test them, and adjust as necessary. Trust me, you will sleep better at night.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Easy Application Deployments with Linux</title>
      <link>https://lackofimagination.org/2024/05/easy-application-deployments-with-linux/</link>
      <pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/05/easy-application-deployments-with-linux/</guid>
      <description>&lt;p&gt;Linux comes with all the basic tools necessary to deploy an application to development and production environments, and to roll back to any past version if something goes wrong. It takes just a few commands to set everything up.&lt;/p&gt;
&lt;h4 id=&#34;creating-application-user&#34;&gt;Creating Application User&lt;/h4&gt;
&lt;p&gt;It&amp;rsquo;s a good idea to create a user with limited privileges to deploy and run your application. Typically, your main user account has root-level access via sudo, which poses a security risk if your application is compromised.&lt;/p&gt;
&lt;p&gt;The following command will create a user named ‘app’ with no password, requiring an SSH key pair to log in for increased security:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;me@server:~$ sudo adduser --disabled-password --gecos &amp;#39;&amp;#39; app
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Adding user `app&amp;#39; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Adding new group `app&amp;#39; (1001) ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Adding new user `app&amp;#39; (1001) with group `app&amp;#39; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Creating home directory `/home/app&amp;#39; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Copying files from `/etc/skel&amp;#39; ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We also need to ensure that the services started by &amp;lsquo;app&amp;rsquo; continue to run after logout:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;me@server:~$ sudo loginctl enable-linger app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can now switch to our newly created account and generate an SSH key pair. It&amp;rsquo;s fine to press Enter to accept the default settings. We will use this key pair exclusively to pull code from our application&amp;rsquo;s Git repository:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;me@server:~$ sudo su - app
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ ssh-keygen
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Generating public/private rsa key pair.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Enter file in which to save the key (/home/app/.ssh/id_rsa): 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Created directory &amp;#39;/home/app/.ssh&amp;#39;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Enter passphrase (empty for no passphrase): 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Enter same passphrase again: 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Your identification has been saved in /home/app/.ssh/id_rsa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Your public key has been saved in /home/app/.ssh/id_rsa.pub
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To view the contents of the public key file, run the following command and copy its output to the online Git repository of your choice, such as GitHub or GitLab, as a deploy key (normally found under Settings):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ cat ~/.ssh/id_rsa.pub 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh-rsa AAAAB3NzaC1yc2EAAAA... app@server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the deploy key is set up, we can clone our application&amp;rsquo;s repository with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ git clone git@github.com:me/my-app.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before we continue, it&amp;rsquo;s important to add the SSH public key used for accessing our regular account to the &amp;lsquo;app&amp;rsquo; account&amp;rsquo;s &lt;em&gt;authorized_keys&lt;/em&gt; file, enabling direct SSH access to &amp;lsquo;app&amp;rsquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ nano ~/.ssh/authorized_keys
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From this point on, we won&amp;rsquo;t be needing our regular user account anymore. The rest of the commands must be run after SSH&amp;rsquo;ing to &amp;lsquo;app&amp;rsquo;.&lt;/p&gt;
&lt;h4 id=&#34;deploy-script&#34;&gt;Deploy Script&lt;/h4&gt;
&lt;p&gt;Since we have our application code in place, it&amp;rsquo;s time to create a deploy script. Here&amp;rsquo;s one for a typical Node.js application:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ nano deploy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/bin/bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;APP_REPO&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/home/app/my-app&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;APP_DEPLOYMENTS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/home/app/deployments&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Exit immediately if a command fails&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;set -e
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Pull changes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd $APP_REPO
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git reset --hard
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git pull
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Install dependencies and run tests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm install
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm run test
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Create new deployment directory and copy the code over&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NOW&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;date --iso-8601&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;minutes | sed &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/:/-/g&amp;#39;&lt;/span&gt; | cut -c 1-16&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p $APP_DEPLOYMENTS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd $APP_DEPLOYMENTS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cp -a $APP_REPO $NOW
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Point to new deployment using a symbolic link&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ln -vfns $NOW current
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Restart app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user restart my-app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We make the deploy script executable with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ chmod +x deploy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the deploy script, we pull changes, install dependencies, run tests, and create a new deployment directory named with the current date and time, such as &lt;em&gt;2024-05-19T16-42&lt;/em&gt;. Finally, we update the symbolic link named &lt;em&gt;current&lt;/em&gt; to point to the new deployment directory and then restart the application.&lt;/p&gt;
&lt;p&gt;After a few executions of our deploy script, the &lt;em&gt;deployments&lt;/em&gt; directory should look like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ ~/deploy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ ls -l ~/deployments/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;drwxrwxr-x 4 app app 4096 May 18 09:53 2024-05-18T09-53
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;drwxrwxr-x 4 app app 4096 May 19 13:25 2024-05-19T13-25
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;drwxrwxr-x 4 app app 4096 May 19 16:42 2024-05-19T16-42
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lrwxrwxrwx 1 app app   16 May 19 16:42 current -&amp;gt; 2024-05-19T16-42
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If anything goes wrong with the deploy script, such as failing tests, no harm will be done because the script exits upon the first error encountered.&lt;/p&gt;
&lt;h4 id=&#34;rolling-back-deployments&#34;&gt;Rolling Back Deployments&lt;/h4&gt;
&lt;p&gt;To roll back to a previous deployment, just two commands are needed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update the symbolic link to point to a different deployment directory.&lt;/li&gt;
&lt;li&gt;Restart the application.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ ln -vfns ~/deployments/2024-05-19T13-25 ~/deployments/current
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ systemctl --user restart my-app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;service-file&#34;&gt;Service File&lt;/h4&gt;
&lt;p&gt;In the deploy script and the rollback example above, we restart our application by using &lt;code&gt;systemctl&lt;/code&gt;, a widely used service manager in Linux. However, we haven&amp;rsquo;t created a service file for our application yet, so let&amp;rsquo;s do that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;app@server:~$ mkdir -p  ~/.config/systemd/user/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;app@server:~$ nano ~/.config/systemd/user/my-app.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;My App&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;WorkingDirectory&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/home/app/deployments/current&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;DB_HOST=&amp;#39;localhost&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;DB_PORT=5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;DB_USERNAME=&amp;#39;my_app&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;DB_PASSWORD=&amp;#39;...&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ExecStart&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/home/app/.nvm/versions/node/v20.13.1/bin/node server.js&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;RestartSec&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Restart&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;multi-user.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The service file is mostly self explanatory. We define the working directory of our application, any environment variables the application needs, the command to start the application, and the application restart behavior in case of a crash.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;WantedBy=multi-user.target&lt;/code&gt; line ensures that our service is launched only after the server can connect to the network in the event of a server restart.&lt;/p&gt;
&lt;p&gt;For a complete list of options, you might want to take a look at &lt;a href=&#34;https://www.freedesktop.org/software/systemd/man/255/systemd.service.html&#34;&gt;Systemd service unit configuration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, we can enable and start our service with the following commands:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ systemctl --user enable my-app
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ systemctl --user start my-app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The status of the service can be checked with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ systemctl --user status my-app
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;● my-app.service - My App
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     Loaded: loaded (/home/app/.config/systemd/user/my-app.service; enabled; vendor preset: enabled)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     Active: active (running) since Sun 2024-05-19 09:27:08 UTC; 4s ago
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   Main PID: 1582880 (node)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     CGroup: /user.slice/user-1001.slice/user@1001.service/my-app.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             └─1582880 /home/app/.nvm/versions/node/v20.13.1/bin/node server.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;May 19 09:27:08 server systemd[1563720]: Started My App.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;May 19 09:27:08 server node[1582880]: Server is running on http://localhost:8000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can review the logs our application generates with &lt;code&gt;journalctl&lt;/code&gt;. For example, the following command shows the last 100 entries:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app@server:~$ journalctl --user -n100 -u my-app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;bonus-putting-our-application-behind-a-web-server&#34;&gt;Bonus: Putting our Application Behind a Web Server&lt;/h4&gt;
&lt;p&gt;Our application is now up and running on http://localhost:8000, but it lacks HTTPS support and features such as HTTP access logs. We can set up a web server as a &lt;a href=&#34;https://en.wikipedia.org/wiki/Reverse_proxy&#34;&gt;reverse proxy&lt;/a&gt; to redirect requests to internal ports.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s switch to our main user account, install the nginx web server, and create a configuration for our application:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;me@server:~$ sudo apt install nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;me@server:~$ sudo nano /etc/nginx/sites-available/default
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The following configuration redirects all HTTPS requests to our application. HTTP requests are automatically redirected to HTTPS as well. If you are using a DNS provider like CloudFlare, you can use the SSL certificate files they provide, or you can use a free SSL certificate service like &lt;a href=&#34;https://certbot.eff.org&#34;&gt;Let&amp;rsquo;s Encrypt&amp;rsquo;s Certbot&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;listen&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;443&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;ssl&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;http2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;listen&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;[::]:443&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;ssl&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;http2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;server_name&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;ssl_certificate&lt;/span&gt;     &lt;span style=&#34;color:#e6db74&#34;&gt;/etc/ssl/certs/example.com.crt&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;ssl_certificate_key&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/etc/ssl/certs/example.com.key&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;location&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;proxy_pass&lt;/span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;http://localhost:8000/&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;proxy_set_header&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;Host&lt;/span&gt; $host;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;proxy_set_header&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;X-Real-IP&lt;/span&gt; $remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;listen&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;server_name&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;301&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;https://&lt;/span&gt;$host$request_uri;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After we&amp;rsquo;ve saved the configuration, it&amp;rsquo;s a good idea to check its integrity with &lt;code&gt;nginx configtest&lt;/code&gt;. If all is well, we can then tell nginx to reload its configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;me@server:~$ sudo service nginx configtest 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * Testing nginx configuration                                                     [ OK ] 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;me@server:~$ sudo service nginx reload
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Web server access and error logs can be found in &lt;em&gt;/var/log/nginx&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id=&#34;wrap-up&#34;&gt;Wrap up&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;ve made application deployments and rollbacks possible with nothing more than the built-in Linux tools. One can easily create multiple environments for development, staging, and production. All it takes is a deploy script, a service file, and a few commands run over SSH.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Back to Basics in Web Apps</title>
      <link>https://lackofimagination.org/2024/04/back-to-basics-in-web-apps/</link>
      <pubDate>Wed, 24 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/04/back-to-basics-in-web-apps/</guid>
      <description>&lt;p&gt;In the beginning, there was only HTML. The first official HTML specification focused on semantic markup. There were minimal &lt;a href=&#34;https://www.w3.org/MarkUp/HTMLPlus/htmlplus_16.html&#34;&gt;styling tags&lt;/a&gt; and &lt;a href=&#34;https://www.w3.org/MarkUp/HTMLPlus/htmlplus_21.html&#34;&gt;attributes&lt;/a&gt;. It was up to the web browser how to render the markup in an HTML document.&lt;/p&gt;
&lt;p&gt;The whole specification was refreshingly simple. You could easily read it in one sitting. Over three decades later, we are still using the same HTML tags &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTML/Element&#34;&gt;and then some&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://www.w3.org/DesignIssues/Principles.html&#34;&gt;Principles of Design&lt;/a&gt;, Tim Berners-Lee, explains why he came up with HTML as the language to define web pages:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Computer Science in the 1960s to 80s spent a lot of effort making languages that were as powerful as possible. Nowadays we have to appreciate the reasons for picking not the most powerful solution but the least powerful. The reason for this is that the less powerful the language, the more you can do with the data stored in that language. If you write it in a simple declarative form, anyone can write a program to analyze it in many ways. (&amp;hellip;) I chose HTML not to be a programming language because I wanted different programs to do different things with it: present it differently, extract tables of contents, index it, and so on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;css&#34;&gt;CSS&lt;/h4&gt;
&lt;p&gt;There were attempts to expand the styling capabilities of HTML with tags like &lt;code&gt;&amp;lt;font&amp;gt;&lt;/code&gt;, but a better solution arrived a few years later in 1996: &lt;a href=&#34;https://www.w3.org/TR/CSS1/&#34;&gt;CSS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CSS enabled the &lt;strong&gt;separation of content and presentation&lt;/strong&gt; by using style sheets. CSS relied on rules (e.g. &lt;code&gt;h1, h2, h3 { font-family: sans-serif }&lt;/code&gt;) to define styles for HTML markup. It was generally easy to understand, except for the concept of &amp;ldquo;specificity&amp;rdquo;, which caused &lt;a href=&#34;https://codepen.io/davidkpiano/post/the-simplicity-of-specificity&#34;&gt;countless headaches&lt;/a&gt; for generations of web developers. As a result, a number of workarounds such as &lt;a href=&#34;https://getbem.com&#34;&gt;BEM&lt;/a&gt; and &lt;a href=&#34;https://tailwindcss.com&#34;&gt;Tailwind&lt;/a&gt; emerged over the years.&lt;/p&gt;
&lt;h4 id=&#34;javascript&#34;&gt;JavaScript&lt;/h4&gt;
&lt;p&gt;As the final cornerstone technology of the web, the confusingly named JavaScript actually arrived shortly before CSS, but became a standard under the name &lt;a href=&#34;https://ecma-international.org/wp-content/uploads/ECMA-262_1st_edition_june_1997.pdf&#34;&gt;ECMAScript&lt;/a&gt; later in 1997.&lt;/p&gt;
&lt;p&gt;Despite the name, JavaScript has virtually nothing to do with Java except for a somewhat similar syntax. Unlike Java, JavaScript has dynamic typing, prototype-based object-orientation, and first-class functions (Java added support for anonymous functions much later).&lt;/p&gt;
&lt;p&gt;JavaScript was designed to add dynamic behavior to otherwise static web pages, but despite its many strengths, it was arguably &lt;a href=&#34;https://www.crockford.com/javascript/javascript.html&#34;&gt;the world&amp;rsquo;s most misunderstood language&lt;/a&gt; for quite some time.&lt;/p&gt;
&lt;h4 id=&#34;ajax&#34;&gt;Ajax&lt;/h4&gt;
&lt;p&gt;JavaScript received a massive boost of popularity from &lt;a href=&#34;https://en.wikipedia.org/wiki/Ajax_(programming)&#34;&gt;Ajax&lt;/a&gt;, a browser technology that allows a web page to exchange data with servers in the background. Before Ajax, many user actions like submitting a form required a complete page refresh. Ajax enabled the &lt;strong&gt;separation of data exchange and presentation&lt;/strong&gt;, and dynamic web apps as we know them today were born!&lt;/p&gt;
&lt;p&gt;The first popular web apps started to appear in the mid-2000s, with Google&amp;rsquo;s Gmail and Maps as prime examples.&lt;/p&gt;
&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/back-to-basics-in-web-apps/jquery.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/back-to-basics-in-web-apps/jquery.png&#34; alt=&#34;jQuery&#34; /&gt;

&lt;h4 id=&#34;jquery&#34;&gt;jQuery&lt;/h4&gt;
&lt;p&gt;As web apps started to appear, the tooling for web apps was seriously lacking. Enter &lt;a href=&#34;https://jquery.com&#34;&gt;jQuery&lt;/a&gt;, a JavaScript library which allowed developers to use CSS selectors to access any &lt;a href=&#34;https://en.wikipedia.org/wiki/Document_Object_Model&#34;&gt;DOM&lt;/a&gt; element. jQuery supports a large number of chainable methods for DOM manipulation, event handling, and Ajax.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a typical piece of jQuery code. It&amp;rsquo;s probably not hard to guess what it does:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;$&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ul.tablist li&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;addClass&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;tab&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;selectTab&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks to its simplicity and power, it&amp;rsquo;s no wonder jQuery is still widely used nearly two decades after its initial release. However, a major issue with jQuery is the close coupling of presentation (DOM elements) and JavaScript logic, which can make maintenance and scalability hard.&lt;/p&gt;
&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/back-to-basics-in-web-apps/angular.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/back-to-basics-in-web-apps/angular.png&#34; alt=&#34;AngularJS&#34; /&gt;

&lt;h4 id=&#34;angularjs&#34;&gt;AngularJS&lt;/h4&gt;
&lt;p&gt;Starting with the 2010s, frameworks specifically designed to create &lt;a href=&#34;https://en.wikipedia.org/wiki/Single-page_application&#34;&gt;Single Page Applications&lt;/a&gt; started to appear with the arrival of &lt;a href=&#34;https://en.wikipedia.org/wiki/AngularJS&#34;&gt;AngularJS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;AngularJS&amp;rsquo;s main selling point was two-way data binding. AngularJS keeps the data model and the corresponding HTML (view) in sync automatically. In other words, whenever one changes, the other changes too. This reduces the need for direct DOM manipulation via libraries like jQuery at the expense of additional complexity and potential performance issues introduced by the framework.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how a simple counter can be implemented in AngularJS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ng-app&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;myApp&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ng-controller&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;counterCtrl as counter&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ng-click&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;counter.increment()&amp;#34;&lt;/span&gt;&amp;gt;Increment&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;Count: {{ counter.count }}&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;angular&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;module&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;myApp&amp;#39;&lt;/span&gt;, []).&lt;span style=&#34;color:#a6e22e&#34;&gt;controller&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;counterCtrl&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/back-to-basics-in-web-apps/react.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/back-to-basics-in-web-apps/react.png&#34; alt=&#34;React&#34; /&gt;

&lt;h4 id=&#34;react&#34;&gt;React&lt;/h4&gt;
&lt;p&gt;React, which emerged a few years after AngularJS, is now the &lt;a href=&#34;https://radar.cloudflare.com/year-in-review/2023#website-technologies&#34;&gt;most popular web frontend library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unlike AngularJS, which uses the existing HTML as a template, React components dynamically generate HTML using JavaScript. JSX, a JavaScript syntax extension, is typically used for HTML generation but requires a transpiling step before use.&lt;/p&gt;
&lt;p&gt;The creators of React &lt;a href=&#34;https://legacy.reactjs.org/docs/introducing-jsx.html&#34;&gt;argued&lt;/a&gt; that &amp;ldquo;instead of artificially separating technologies by putting markup and logic in separate files, React separates concerns with loosely coupled units called &amp;lsquo;components&amp;rsquo; that contain both.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Under the hood, React utilizes a virtual DOM to optimize updates, altering only the parts of the browser DOM that have changed, thus enhancing performance. There&amp;rsquo;s no direct support for two-way data-binding, but it&amp;rsquo;s possible to achieve a similar result by other means.&lt;/p&gt;
&lt;p&gt;The following example is a counter implemented in React with hooks, a later addition to the library:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import React, { useState } from &amp;#39;react&amp;#39;;

const App = () =&amp;gt; {
    const [counter, setCounter] = useState(0);
    const increment = () =&amp;gt; {
        setCounter(counter + 1);
    };
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;button onClick={increment}&amp;gt;Increment&amp;lt;/button&amp;gt;
            &amp;lt;div&amp;gt;Count: {counter}&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/back-to-basics-in-web-apps/vue.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/back-to-basics-in-web-apps/vue.png&#34; alt=&#34;Vue.js&#34; /&gt;

&lt;h4 id=&#34;vuejs&#34;&gt;Vue.js&lt;/h4&gt;
&lt;p&gt;Shortly after React came out, Evan You created Vue.js with the goal of creating a lightweight version of AngularJS. It&amp;rsquo;s currently the &lt;a href=&#34;https://radar.cloudflare.com/year-in-review/2023#website-technologies&#34;&gt;second most-popular web frontend framework&lt;/a&gt;, following React. Just like React, Vue internally uses a virtual DOM, but it also supports two-way binding.&lt;/p&gt;
&lt;p&gt;Vue supports single-file components (SFCs), which encapsulate HTML templates, JavaScript, and CSS in one file, keeping them clearly separated. The following example is in SFC format, using Vue&amp;rsquo;s original Options API. Vue also supports a new API called &amp;ldquo;Composition&amp;rdquo; that works similar to React hooks.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;increment&amp;#34;&lt;/span&gt;&amp;gt;Increment&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;Count: {{ count }}&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;methods&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;style&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;scoped&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;blue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Other popular web frontend frameworks include &lt;a href=&#34;https://angular.dev&#34;&gt;Angular&lt;/a&gt;, which is quite different than the original AngularJS, and &lt;a href=&#34;https://svelte.dev&#34;&gt;Swelte&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;bundlers&#34;&gt;Bundlers&lt;/h4&gt;
&lt;p&gt;Web bundlers such as &lt;a href=&#34;https://esbuild.github.io&#34;&gt;esbuild&lt;/a&gt; and &lt;a href=&#34;https://webpack.js.org&#34;&gt;webpack&lt;/a&gt; are often used to manage the varying levels of support for JavaScript and CSS features across web browsers. These tools transpile modern JavaScript and CSS code to formats old web browsers can understand.&lt;/p&gt;
&lt;p&gt;Bundlers also decrease the size of JavaScript and CSS code by performing tasks like tree shaking for unused code elimination, removing whitespace, and shortening variable names.&lt;/p&gt;
&lt;p&gt;Bundlers typically consolidate hundreds or even thousands of JavaScript and CSS files from a project into a few optimized files for easier distribution.&lt;/p&gt;
&lt;p&gt;Until recently, using web bundlers as code transpiling tools was mostly a necessity, but with the &lt;a href=&#34;https://blogs.windows.com/windowsexperience/2022/06/15/internet-explorer-11-has-retired-and-is-officially-out-of-support-what-you-need-to-know/&#34;&gt;retirement of Internet Explorer 11&lt;/a&gt;, modern evergreen browsers (Chrome, Edge, Firefox, and Safari) no longer require transpilation.&lt;/p&gt;
&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/back-to-basics-in-web-apps/progressive-enhancement-web-design-pyramid.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/back-to-basics-in-web-apps/progressive-enhancement-web-design-pyramid.png&#34; alt=&#34;Progressive Enhancement&#34; /&gt;

&lt;h4 id=&#34;progressive-enhancement&#34;&gt;Progressive Enhancement&lt;/h4&gt;
&lt;p&gt;The idea of &lt;a href=&#34;https://en.wikipedia.org/wiki/Progressive_enhancement&#34;&gt;progressive enhancement&lt;/a&gt; is about prioritizing HTML content over everything else. Progressive enhancement has been around for quite some time, but gained renewed attention with the proliferation of frontend frameworks.&lt;/p&gt;
&lt;p&gt;Not all web apps are single-page applications, and not all single-page applications are complex enough to warrant a full-blown frontend framework.&lt;/p&gt;
&lt;p&gt;Starting with HTML and CSS, and enhancing them with lightweight JavaScript frameworks like &lt;a href=&#34;https://alpinejs.dev&#34;&gt;Alpine.js&lt;/a&gt; and &lt;a href=&#34;https://htmx.org&#34;&gt;htmx&lt;/a&gt; offers numerous advantages, including faster initial load times, better accessibility, easier indexing of page content by search engines, and reduced or no need for bundlers.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s our counter implemented in Alpine.js:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x-data&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;{ count: 0 }&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;count++&amp;#34;&lt;/span&gt;&amp;gt;Increment&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;Count: &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x-text&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;count&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img class=&#34;figure-img&#34; style=&#34;float: right; shape-outside: url(/images/back-to-basics-in-web-apps/alpine.png); shape-margin: 1em; shape-image-threshold: 0.8;&#34; src=&#34;https://lackofimagination.org/images/back-to-basics-in-web-apps/alpine.png&#34; alt=&#34;Alpine.js&#34; /&gt;

Although Alpine encourages &amp;ldquo;sprinkling&amp;rdquo; JavaScript into HTML as a form of progressive enhancement, the above example can be expanded into a reusable component, as shown below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x-data&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;counter&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;increment&amp;#34;&lt;/span&gt;&amp;gt;Increment&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;Count: &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x-text&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;count&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;app&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	    &lt;span style=&#34;color:#a6e22e&#34;&gt;Alpine&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;counter&amp;#39;&lt;/span&gt;, () =&amp;gt; ({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	        &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	        &lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	    }));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	document.&lt;span style=&#34;color:#a6e22e&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;alpine:init&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;app&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;too-much-of-a-good-thing&#34;&gt;Too Much of a Good Thing&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;ve come a long way since the early days of the web, yet we are still using the same three core technologies: HTML, CSS, and JavaScript, all of which have vastly improved over the past three decades. To give a few examples, HTML now natively supports web components, CSS layouts have been enhanced by flexbox and grid, and JavaScript proxies have simplified the implementation of two-way binding.&lt;/p&gt;
&lt;p&gt;Despite this progress, it seems we rely too much on JavaScript, the most powerful of the three core web technologies. Had Tim Berners-Lee chosen a more powerful language as the foundation of the web, instead of HTML, would the web have gained as much traction? How would search engines even work? They couldn&amp;rsquo;t merely parse web pages; they would need to execute code and evaluate the output somehow. Does anyone even remember websites built &lt;em&gt;entirely&lt;/em&gt; with &lt;a href=&#34;https://en.wikipedia.org/wiki/Adobe_Flash&#34;&gt;Flash&lt;/a&gt;, let alone &lt;a href=&#34;https://en.wikipedia.org/wiki/Java_applet&#34;&gt;Java applets&lt;/a&gt;?&lt;/p&gt;
&lt;h4 id=&#34;coming-full-circle&#34;&gt;Coming Full Circle&lt;/h4&gt;
&lt;p&gt;As much as I like JavaScript, I think it&amp;rsquo;s time to return to the basics. I find it refreshing to write, not generate, HTML and CSS, and then sprinkle some JavaScript for interactivity. I&amp;rsquo;m sure many web developers would feel the same. Let&amp;rsquo;s give it a try.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Beyond Foreign Keys</title>
      <link>https://lackofimagination.org/2024/04/beyond-foreign-keys/</link>
      <pubDate>Fri, 05 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/04/beyond-foreign-keys/</guid>
      <description>&lt;p&gt;In a relational database, foreign keys are normally used to associate records stored in different tables. Let&amp;rsquo;s say we want to add a forum to our website where people can discuss matters related to our product. To keep things simple, we will model our forum with just three tables: &lt;strong&gt;user&lt;/strong&gt;, &lt;strong&gt;post&lt;/strong&gt; and &lt;strong&gt;topic&lt;/strong&gt;. The relationships between the tables can be modeled by asking a few questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Who wrote a given post? &lt;strong&gt;post&lt;/strong&gt;.&lt;em&gt;user_id&lt;/em&gt; references &lt;strong&gt;user&lt;/strong&gt;.&lt;em&gt;id&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Which posts are under a given topic? &lt;strong&gt;post&lt;/strong&gt;.&lt;em&gt;topic_id&lt;/em&gt; references &lt;strong&gt;topic&lt;/strong&gt;.&lt;em&gt;id&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Who started a given topic? &lt;strong&gt;topic&lt;/strong&gt;.&lt;em&gt;user_id&lt;/em&gt; references &lt;strong&gt;user&lt;/strong&gt;.&lt;em&gt;id&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/user-post-topic-er-diagram.png&#34;  alt=&#34;Using foreign keys to associate tables for a simple forum.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Using foreign keys to associate tables for a simple forum.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;This is all well and good, but each relationship requires adding a new column to a table, and we don&amp;rsquo;t even have many-to-many relationships in this example, which will necessitate creating additional tables.&lt;/p&gt;
&lt;p&gt;Wouldn&amp;rsquo;t it be nice to define relationships dynamically without having to add extra columns or tables? And while we&amp;rsquo;re at it, how about having &lt;em&gt;sparse relationships&lt;/em&gt; by associating a record &lt;em&gt;directly&lt;/em&gt; with any other record like &amp;ldquo;post X was last edited by user #123&amp;rdquo; or &amp;ldquo;post X was flagged for review by user #456&amp;rdquo; (who happens to be a moderator)?&lt;/p&gt;
&lt;p&gt;With that idea in mind, I&amp;rsquo;ve created &lt;a href=&#34;https://www.npmjs.com/package/tie-in&#34;&gt;Tie-in&lt;/a&gt;, a relational data component library for Node.js. Tie-in lets you store and query records that can be related to any other record.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s create a user, a post, and a topic by using data components (more on how to define them later) in the relational database of our choice (in this case PostgreSQL):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dbConfig&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pg&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;connection&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;postgresql://localhost/me&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Load Tie-in and data component definitions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;tie-in&amp;#39;&lt;/span&gt;)(&lt;span style=&#34;color:#a6e22e&#34;&gt;dbConfig&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;./components/user&amp;#39;&lt;/span&gt;)(&lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;post&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;./components/post&amp;#39;&lt;/span&gt;)(&lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;./components/topic&amp;#39;&lt;/span&gt;)(&lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firstSteps&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Register the components we will use
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;register&lt;/span&gt;([&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;post&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Create a user named Asuka
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Asuka&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;asuka@localhost&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;country&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;JP&amp;#39;&lt;/span&gt; }));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Create a post and make its author Asuka
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;postId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;post&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Hi!&amp;#39;&lt;/span&gt; }), {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;upstream&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userId&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;relType&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;author&amp;#39;&lt;/span&gt; })],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Create a topic and make the topic starter Asuka, also make the post a child of this topic
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;First Topic&amp;#39;&lt;/span&gt; }), {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;upstream&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userId&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;relType&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;starter&amp;#39;&lt;/span&gt; })],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;downstream&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#a6e22e&#34;&gt;post&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;postId&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;relType&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;child&amp;#39;&lt;/span&gt; })],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Retrieve topic and related records
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topicRecs&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt; }));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;JSON&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringify&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;topicRecs&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;firstSteps&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the individual records are in place, we retrieve the newly created topic with &lt;strong&gt;tie.get&lt;/strong&gt;, which recursively retrieves all related records and groups them together. In Tie-in, related records can be upstream (referencing a record) and/or downstream (referenced from another record). Related records can optionally have types specified by &lt;em&gt;relType&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;topic&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;self&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;First Topic&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2024-03-28T12:27:51.542Z&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;updatedAt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2024-03-28T12:27:51.542Z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;self&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;relType&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;starter&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Asuka&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;asuka@localhost&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;country&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JP&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2024-03-28T12:27:51.531Z&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;updatedAt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2024-03-28T12:27:51.531Z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;post&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;self&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;relType&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;child&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hi!&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2024-03-28T12:27:51.538Z&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;updatedAt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2024-03-28T12:27:51.538Z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;defining-components&#34;&gt;Defining Components&lt;/h4&gt;
&lt;p&gt;To define a component, you call &lt;strong&gt;tie.define&lt;/strong&gt; with the following arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;name:&lt;/strong&gt; Name of the component&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;schema:&lt;/strong&gt; A function that defines the database table schema. Tie-in uses &lt;a href=&#34;https://knexjs.org&#34;&gt;knex&lt;/a&gt; under the hood. Table field names must be in snake_case for maximum compatibility across different database systems. Tie-in does the snake_case to camelCase conversions and vice versa automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;data:&lt;/strong&gt; A function that accepts an object with field names in camelCase, maps those fields to the database table fields created with schema, and returns the resulting object.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In its simplest form, a component definition should look like the example below. The only requirement is that there must be a field named &lt;em&gt;id&lt;/em&gt; that uniquely identifies each record.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;exports&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;post&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;schema&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;knex&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;tablePrefix&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tableName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tablePrefix&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;knex&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;schema&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;hasTable&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;tableName&lt;/span&gt;))) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;knex&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;schema&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;createTable&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;tableName&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;table&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;table&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;increments&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;primary&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;table&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;content&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;notNullable&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;table&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timestamps&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;created_at&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;createdAt&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;updated_at&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;updatedAt&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;define&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;schema&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You might probably want to validate the input passed to the &lt;strong&gt;data&lt;/strong&gt; function. Tie-in uses &lt;a href=&#34;https://www.npmjs.com/package/fn-arg-validator&#34;&gt;fn-arg-validator&lt;/a&gt; for internal data validation, and exposes it through &lt;strong&gt;tie.is&lt;/strong&gt;, but you can of course use any other library you would like.&lt;/p&gt;
&lt;h3 id=&#34;tieget&#34;&gt;tie.get&lt;/h3&gt;
&lt;p&gt;Syntax: &lt;code&gt;tie.get(comp, filters = {})&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When you pass a component instance (&lt;em&gt;comp&lt;/em&gt;) to &lt;strong&gt;tie.get&lt;/strong&gt;, it uses the component instance&amp;rsquo;s data for search. Here are some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;await tie.get(user( {country: &#39;JP&#39;} ))&lt;/code&gt; returns the users from Japan.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await tie.get(user( {country: &#39;JP&#39;, username: &#39;Asuka&#39;} ))&lt;/code&gt; returns the users from Japan having the username &amp;lsquo;Asuka&amp;rsquo;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await tie.get(user())&lt;/code&gt; returns all users.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;tie.get&lt;/strong&gt; supports a wide range of options to filter the results returned. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getPostCountsGroupedByUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;filters&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;aggregate&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [{ &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;count&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt; }],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;by&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;columns&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;] },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;filterUpstreamBy&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topicId&lt;/span&gt; })],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;orderBy&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [{ &lt;span style=&#34;color:#a6e22e&#34;&gt;column&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt; }],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tie&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;post&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;filters&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For a complete list of the available functions and options, you can visit &lt;a href=&#34;https://github.com/aycangulez/tie-in&#34;&gt;Tie-in&amp;rsquo;s GitHub page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;how-is-this-different-than-an-orm&#34;&gt;How is this Different than an ORM?&lt;/h3&gt;
&lt;p&gt;Tie-in isn&amp;rsquo;t an ORM (Object-Relational Mapper). In fact, Tie-in&amp;rsquo;s data component instances are immutable &amp;ndash; once set, you can&amp;rsquo;t change the values a component instance holds.&lt;/p&gt;
&lt;p&gt;Tie-in components have embedded schema definitions. In a sense, they are self-contained.&lt;/p&gt;
&lt;p&gt;Tie-in doesn&amp;rsquo;t suffer from the worst performance issues that plague various ORMs such as the infamous N+1 query problem. Tie-in also tries its best to reduce the number of queries it generates by merging related queries together.&lt;/p&gt;
&lt;h3 id=&#34;can-i-still-use-foreign-keys&#34;&gt;Can I still use Foreign Keys?&lt;/h3&gt;
&lt;p&gt;Absolutely. There&amp;rsquo;s nothing preventing you from using them. Foreign keys are great! Tie-in doesn&amp;rsquo;t replace foreign keys, but supplements them.&lt;/p&gt;
&lt;h3 id=&#34;fun-with-relationships&#34;&gt;Fun with Relationships&lt;/h3&gt;
&lt;p&gt;In my opinion, Tie-in makes it fun to write database-level code by removing the need to define relationships for anything and everything. You can easily have an ad-hoc relationship between two records and nothing else.&lt;/p&gt;
&lt;p&gt;Tie-in&amp;rsquo;s recursive record retrieval functionality makes it almost too easy to get the data you need without having to worry about all the joins, and dare I say, it&amp;rsquo;s a little like magic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href=&#34;https://github.com/aycangulez/tie-in&#34;&gt;Tie-in&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Software Architectures in a Nutshell</title>
      <link>https://lackofimagination.org/2024/03/software-architectures-in-a-nutshell/</link>
      <pubDate>Tue, 26 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/03/software-architectures-in-a-nutshell/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;All problems in computer science can be solved by another level of indirection.&amp;quot; &amp;ndash; Butler Lampson&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The great majority of the software architectures currently in use are variations of the layered architecture, and what really sets them apart is the implementation details. Some might find this statement controversial, but in my experience, most software applications rely on code organized in layers to manage complexity. Some of the layers may utilize message queues or microservices, but that doesn&amp;rsquo;t necessarily make the architecture event or microservices based.&lt;/p&gt;
&lt;h4 id=&#34;in-n-out&#34;&gt;In-n-Out&lt;/h4&gt;
&lt;p&gt;All non-trivial computer programs, regardless of their complexity, accept some input, do something with it, and produce an output.&lt;/p&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 544 89&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 288,0 L 320,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,16 L 264,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 248,48 L 272,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,64 L 320,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,16 L 272,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,16 L 336,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;256.000000,48.000000 244.000000,42.400002 244.000000,53.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 248.000000, 48.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;272.000000,16.000000 260.000000,10.400000 260.000000,21.600000&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 264.000000, 16.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 288,0 A 16,16 0 0,0 272,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,0 A 16,16 0 0,1 336,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,48 A 16,16 0 0,0 288,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,48 A 16,16 0 0,1 320,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;184&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;O&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;192&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;192&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;200&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;200&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;224&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;224&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
&lt;h4 id=&#34;modules&#34;&gt;Modules&lt;/h4&gt;
&lt;p&gt;Most programs have some internal structure in the form of modules. Modules can be implemented by using functions, objects, or a mixture of both. Ideally, each module should be responsible for a well defined task and nothing else. This principle is called &lt;a href=&#34;https://en.wikipedia.org/wiki/Separation_of_concerns&#34;&gt;Separation of Concerns&lt;/a&gt;.&lt;/p&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 544 217&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 288,0 L 320,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 320,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,48 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,80 L 264,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 320,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 248,112 L 272,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,112 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,144 L 320,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,176 L 320,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,192 L 320,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,16 L 272,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,112 L 272,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 288,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 288,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,144 L 288,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,16 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,80 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,144 L 320,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,16 L 336,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,96 L 336,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;256.000000,112.000000 244.000000,106.400002 244.000000,117.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 248.000000, 112.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;272.000000,80.000000 260.000000,74.400002 260.000000,85.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 264.000000, 80.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 288,0 A 16,16 0 0,0 272,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,0 A 16,16 0 0,1 336,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,176 A 16,16 0 0,0 288,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,176 A 16,16 0 0,1 320,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;184&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;O&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;192&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;192&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;200&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;200&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;224&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;224&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
&lt;h4 id=&#34;layers&#34;&gt;Layers&lt;/h4&gt;
&lt;p&gt;Once you reach a certain number of modules, it often makes sense to introduce a hierarchy, and put related modules in separate layers. This is known as &lt;a href=&#34;https://en.wikipedia.org/wiki/Multitier_architecture&#34;&gt;Multi-tier Architecture&lt;/a&gt;. Notice the direction of arrows. Communication is normally unidirectional, and usually obeys the following rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modules can call the other modules in the same layer.&lt;/li&gt;
&lt;li&gt;They can also call the modules in the layer directly below.&lt;/li&gt;
&lt;li&gt;The modules in lower layers cannot call the modules in upper layers at all. In fact, they don&amp;rsquo;t even know if an upper layer actually exists unless they receive a call from it.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 544 217&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 192,0 L 224,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,0 L 320,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,0 L 416,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,16 L 224,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 320,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,16 L 416,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,48 L 224,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,48 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,48 L 416,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 144,80 L 168,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,80 L 224,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 320,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,80 L 416,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,96 L 264,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,96 L 360,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 152,112 L 176,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,112 L 224,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,112 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,112 L 416,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,144 L 224,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,144 L 320,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,144 L 416,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,176 L 224,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,176 L 320,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,176 L 416,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,192 L 224,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,192 L 320,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,192 L 416,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,16 L 176,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,112 L 176,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,16 L 192,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,80 L 192,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,144 L 192,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,16 L 224,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,80 L 224,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,144 L 224,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,16 L 240,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,96 L 240,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,16 L 272,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 288,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 288,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,144 L 288,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,16 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,80 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,144 L 320,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,16 L 336,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,96 L 336,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 368,16 L 368,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,16 L 384,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,80 L 384,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,144 L 384,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,16 L 416,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,80 L 416,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,144 L 416,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 432,16 L 432,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;160.000000,112.000000 148.000000,106.400002 148.000000,117.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 152.000000, 112.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;176.000000,80.000000 164.000000,74.400002 164.000000,85.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 168.000000, 80.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;272.000000,96.000000 260.000000,90.400002 260.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 264.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;368.000000,96.000000 356.000000,90.400002 356.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 360.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 192,0 A 16,16 0 0,0 176,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,0 A 16,16 0 0,1 240,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,0 A 16,16 0 0,0 272,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,0 A 16,16 0 0,1 336,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,0 A 16,16 0 0,0 368,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,0 A 16,16 0 0,1 432,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,176 A 16,16 0 0,0 192,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,176 A 16,16 0 0,1 224,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,176 A 16,16 0 0,0 288,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,176 A 16,16 0 0,1 320,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 368,176 A 16,16 0 0,0 384,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 432,176 A 16,16 0 0,1 416,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;O&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
&lt;p&gt;Since the modules in a layer cannot make any assumptions about the structure of upper layers, it&amp;rsquo;s possible to make changes to the individual layers without affecting others. You can even completely replace a layer with something else. For example, the calls from the upper layer can come from a web page or a mobile app, and the lower layer answering those calls doesn&amp;rsquo;t care where they come from as long as they are in a format it can understand.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t follow the general rules above, layers will eventually intertwine, and as a result, your code will likely turn into spaghetti or a big ball of mud. Yes, these are technical terms :)&lt;/p&gt;
&lt;h4 id=&#34;events&#34;&gt;Events&lt;/h4&gt;
&lt;p&gt;At &lt;a href=&#34;https://faradai.ai&#34;&gt;Faradai&lt;/a&gt;, we collect millions of sensor readings from thousands of IoT devices every day, but we don&amp;rsquo;t process them the moment they arrive at our data collection layer. We store the readings in a message queue first, and process them at a rate we know our servers can comfortably handle. Otherwise, unexpected spikes in traffic could place a heavy burden on the servers.&lt;/p&gt;
&lt;p&gt;You can think of message queues as an example of event-based architecture in which the so-called producers (in our case, IoT devices) store events (sensor readings) that are then consumed by the respective modules in our data processing layer.&lt;/p&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 624 137&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 120,16 L 128,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 168,32 L 216,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 216,32 L 280,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 280,32 L 344,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 344,32 L 408,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 408,32 L 456,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 168,48 L 200,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 232,48 L 264,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 296,48 L 328,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 360,48 L 392,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 120,64 L 136,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 472,64 L 488,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 168,80 L 200,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 232,80 L 264,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 296,80 L 328,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 360,80 L 392,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 168,96 L 216,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 216,96 L 280,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 280,96 L 344,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 344,96 L 408,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 408,96 L 456,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 120,112 L 128,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 152,48 L 152,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 168,48 L 168,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 200,48 L 200,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 216,32 L 216,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 232,48 L 232,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 264,48 L 264,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 280,32 L 280,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 296,48 L 296,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 328,48 L 328,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 344,32 L 344,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 360,48 L 360,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 392,48 L 392,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 408,32 L 408,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 472,48 L 472,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 472,64 L 472,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 128,112 L 144,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 472,64 L 488,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 128,16 L 144,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 472,64 L 488,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;144.000000,64.000000 132.000000,58.400002 132.000000,69.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 136.000000, 64.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;156.000000,48.000000 144.000000,42.400002 144.000000,53.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(60.000000, 144.000000, 48.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;156.000000,80.000000 144.000000,74.400002 144.000000,85.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(300.000000, 144.000000, 80.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;500.000000,32.000000 488.000000,26.400000 488.000000,37.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(300.000000, 488.000000, 32.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;496.000000,64.000000 484.000000,58.400002 484.000000,69.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 488.000000, 64.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;500.000000,96.000000 488.000000,90.400002 488.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(60.000000, 488.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 168,32 A 16,16 0 0,0 152,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 456,32 A 16,16 0 0,1 472,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 152,80 A 16,16 0 0,0 168,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 472,80 A 16,16 0 0,1 456,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;circle cx=&#39;184&#39; cy=&#39;64&#39; r=&#39;6&#39; stroke=&#39;currentColor&#39; fill=&#39;currentColor&#39;&gt;&lt;/circle&gt;
&lt;circle cx=&#39;248&#39; cy=&#39;64&#39; r=&#39;6&#39; stroke=&#39;currentColor&#39; fill=&#39;#fff&#39;&gt;&lt;/circle&gt;
&lt;circle cx=&#39;312&#39; cy=&#39;64&#39; r=&#39;6&#39; stroke=&#39;currentColor&#39; fill=&#39;currentColor&#39;&gt;&lt;/circle&gt;
&lt;circle cx=&#39;376&#39; cy=&#39;64&#39; r=&#39;6&#39; stroke=&#39;currentColor&#39; fill=&#39;currentColor&#39;&gt;&lt;/circle&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;1&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;2&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;52&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;3&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;4&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;5&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;6&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;7&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;504&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;504&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;504&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;512&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;512&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;512&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;520&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;520&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;520&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;528&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;528&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;528&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;536&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;536&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;536&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;544&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;544&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;544&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;552&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;552&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;552&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;560&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;560&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;560&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;1&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;2&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;3&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
&lt;p&gt;We could have sent the sensor readings directly from our data collection layer to the data processing layer, and the early versions of our platform had actually worked that way, but the amount of data we processed increased significantly over time, so we put the message queue as an intermediate layer between the two.&lt;/p&gt;
&lt;h4 id=&#34;microservices&#34;&gt;Microservices&lt;/h4&gt;
&lt;p&gt;When you &amp;ldquo;carve out&amp;rdquo; some of your modules or even layers and turn them into independent programs that you can call from your main application or any other application for that matter, you get yourself microservices.&lt;/p&gt;
&lt;p&gt;Unlike modules that rely on the environment provided by your application, microservices are standalone entities. The usual way to access them is over the network, so it&amp;rsquo;s easier to place them in separate servers. They can be developed separately by different teams in different programming languages or databases if necessary.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a price for the flexibility offered by microservices though. Instead of a single application to worry about, you now have multiple dependencies that may go down or be slow to respond, which can negatively affect the responsiveness of your main application.&lt;/p&gt;
&lt;p&gt;There are tools to &amp;ldquo;orchestrate&amp;rdquo; multiple microservices such as Kubernetes, but it&amp;rsquo;s a good idea to do a cost-benefit analysis before committing to microservices as the added complexity might not justify the potential benefits.&lt;/p&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 544 217&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 192,0 L 224,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,16 L 224,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 320,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,16 L 416,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,32 L 376,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,48 L 224,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,48 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,48 L 416,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 144,80 L 168,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,80 L 224,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,80 L 264,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 320,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,96 L 280,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 152,112 L 176,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,112 L 224,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,112 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,144 L 224,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,176 L 224,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,192 L 224,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,16 L 176,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,112 L 176,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,16 L 192,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,80 L 192,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 192,144 L 192,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,16 L 224,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,80 L 224,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,144 L 224,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,16 L 240,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,80 L 240,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,96 L 240,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,16 L 288,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,80 L 288,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 288,96 L 288,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,16 L 320,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,32 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,80 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,16 L 384,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 384,32 L 384,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 416,16 L 416,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 264,80 L 280,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;160.000000,112.000000 148.000000,106.400002 148.000000,117.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 152.000000, 112.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;176.000000,80.000000 164.000000,74.400002 164.000000,85.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 168.000000, 80.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;292.000000,48.000000 280.000000,42.400002 280.000000,53.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(300.000000, 280.000000, 48.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;288.000000,96.000000 276.000000,90.400002 276.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 280.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;384.000000,32.000000 372.000000,26.400000 372.000000,37.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 376.000000, 32.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 192,0 A 16,16 0 0,0 176,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,0 A 16,16 0 0,1 240,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,176 A 16,16 0 0,0 192,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 240,176 A 16,16 0 0,1 224,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;O&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;S&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;S&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;S&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
&lt;h4 id=&#34;plug-ins&#34;&gt;Plug-ins&lt;/h4&gt;
&lt;p&gt;Plug-ins offer a way to dynamically change the behavior of an application through components that act on the data provided by the application (aka host). For example, at Faradai, we generate alarms upon detecting abnormal energy consumption patterns based on a complex set of rules unique to each client. Since every alarm scenario can be unique, we provide a plug-in architecture called RulesEngine. Each alarm scenario can be thought of as a plug-in that is coded in an embedded scripting language, and can retrieve and process data from the host.&lt;/p&gt;
&lt;p&gt;Notice the bidirectional nature of the arrows between the application and plug-ins. Plug-ins have access to various application modules, and supplement the capabilities of the application.&lt;/p&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 512 217&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 224,0 L 256,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,0 L 352,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,16 L 256,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,16 L 352,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,48 L 256,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,48 L 352,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 176,80 L 200,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,80 L 256,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,80 L 352,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 280,96 L 296,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 184,112 L 208,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,112 L 256,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,112 L 352,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,144 L 256,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,144 L 352,144&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,176 L 256,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,176 L 352,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,192 L 256,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,192 L 352,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 208,16 L 208,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 208,112 L 208,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,16 L 224,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,80 L 224,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 224,144 L 224,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 256,16 L 256,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 256,80 L 256,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 256,144 L 256,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,16 L 272,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 304,16 L 304,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,16 L 320,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,80 L 320,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,144 L 320,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 352,16 L 352,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 352,80 L 352,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 352,144 L 352,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 368,16 L 368,96&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 368,96 L 368,176&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;192.000000,112.000000 180.000000,106.400002 180.000000,117.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 184.000000, 112.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;208.000000,80.000000 196.000000,74.400002 196.000000,85.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 200.000000, 80.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;288.000000,96.000000 276.000000,90.400002 276.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 280.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;304.000000,96.000000 292.000000,90.400002 292.000000,101.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 296.000000, 96.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 224,0 A 16,16 0 0,0 208,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 256,0 A 16,16 0 0,1 272,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 320,0 A 16,16 0 0,0 304,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 352,0 A 16,16 0 0,1 368,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 208,176 A 16,16 0 0,0 224,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 272,176 A 16,16 0 0,1 256,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 304,176 A 16,16 0 0,0 320,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 368,176 A 16,16 0 0,1 352,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;O&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;136&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;136&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;144&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;144&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;152&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;152&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;160&#39; y=&#39;84&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;160&#39; y=&#39;116&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;240&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;240&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;240&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;336&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;336&#39; y=&#39;100&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;336&#39; y=&#39;164&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
&lt;h4 id=&#34;other-architectures&#34;&gt;Other Architectures&lt;/h4&gt;
&lt;p&gt;There are software architectures other than the ones we&amp;rsquo;ve gone over so far, but as we approach software architecture from a code organization perspective, these are the major ones. Other architectures such as &lt;a href=&#34;https://en.wikipedia.org/wiki/Pipeline_(software)&#34;&gt;pipes&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Representational_state_transfer&#34;&gt;REST&lt;/a&gt; focus more on data flow and access respectively rather than code organization.&lt;/p&gt;
&lt;h4 id=&#34;layers-events-microservices-plug-ins-all-combined&#34;&gt;Layers, Events, Microservices, Plug-ins all Combined&lt;/h4&gt;
&lt;p&gt;There is a time and place for all architectures, and they can usually live together as a hybrid architecture. It&amp;rsquo;s probably not a good idea to exclusively commit to a single architecture as each architecture has its strengths and weaknesses, and we can get the most out of them by using each one in places where they excel.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Making Technical Decisions</title>
      <link>https://lackofimagination.org/2024/03/making-technical-decisions/</link>
      <pubDate>Tue, 05 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/03/making-technical-decisions/</guid>
      <description>&lt;p&gt;Over the past two decades, I&amp;rsquo;ve worked as a software developer, tech lead, and CTO at various startups, including my own. I&amp;rsquo;ve accumulated a number of principles I use to make major technical decisions. Most of the principles outlined here were learned the hard way. You might not fully agree with all of the principles, but I hope at least some of them could help those who struggle with tough technical decisions.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;focus-on-your-core-business&#34;&gt;Focus on your core business&lt;/h4&gt;
&lt;p&gt;Before implementing a new functionality, the very first question you should ask is whether that functionality will be a part of your company&amp;rsquo;s core business. If the answer is no, you should look for a third-party library or tool, preferably open-source. If the available options on the market don&amp;rsquo;t quite solve your needs, then you might consider implementing the functionality yourself, but only after doing a careful cost-benefit analysis. Whenever possible, avoid writing new code &amp;ndash; the best code is no code.&lt;/p&gt;
&lt;p&gt;When I first joined &lt;a href=&#34;https://faradai.ai&#34;&gt;Faradai&lt;/a&gt;, the company had the first version of its energy management software platform up and running, successfully serving customers. The platform generally worked fine, but AML, its custom web frontend framework, was hard to use and extend.&lt;/p&gt;
&lt;p&gt;Since no other company used AML, there weren&amp;rsquo;t any outside developers exposed to it, so we had to train new developers before they became productive in it. Worse, some of the developers complained that their newly learned AML skills wouldn&amp;rsquo;t be useful elsewhere, and you know what, they are right!&lt;/p&gt;
&lt;p&gt;In a startup, time to market is critical, and training new developers slowed us down. Had we used a popular frontend framework, we could have simply hired React or Vue developers and had them write code from day one.&lt;/p&gt;
&lt;p&gt;Finally, when our AML framework couldn&amp;rsquo;t do something we needed, we had to stop working on the core application code and work on AML instead. As a software company operating in the carbon accounting and energy management domains, maintaining a custom frontend framework was clearly not our core business.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;no-rewrites-unless-absolutely-necessary&#34;&gt;No rewrites unless absolutely necessary&lt;/h4&gt;
&lt;p&gt;One of the first major technical decisions I made at Faradai was to switch from AML, our custom web frontend framework, to &lt;a href=&#34;https://vuejs.org&#34;&gt;Vue&lt;/a&gt;. However, there was a lot of code written in AML, so the decision to rewrite the frontend in Vue wasn&amp;rsquo;t made lightly as explained above. Since the rewrite would take some time, we did the bare-minimum to maintain AML until we reached feature parity in the Vue version, which also added features like dashboard widgets that could be dragged and dropped anywhere on the screen, something AML lacked.&lt;/p&gt;
&lt;p&gt;Although we had to rewrite the frontend code, the backend stayed mostly as it was before because the code was solid and needed just a bit of refactoring and performance tweaks.&lt;/p&gt;
&lt;p&gt;Rewriting large sections of code is highly risky. Not only is it easy to break existing functionality, but it wastes precious time that could have been used to add new features users need.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;have-a-simple-tech-stack&#34;&gt;Have a simple tech stack&lt;/h4&gt;
&lt;p&gt;We selected Vue over React because, &lt;em&gt;for us&lt;/em&gt;, Vue was simpler to learn and use. There were other, even simpler web frontend frameworks, but they didn&amp;rsquo;t quite meet our needs. As Einstein had once said, &amp;ldquo;Everything should be made as simple as possible, but no simpler,&amp;rdquo; it&amp;rsquo;s best to avoid introducing unnecessary complexity since it might come back to bite you in the future in the form of high maintenance and financial costs.&lt;/p&gt;
&lt;p&gt;When in doubt, choose a monolith over a bunch of microservices, a load balanced application server over a Kubernetes cluster, PostgreSQL over the database flavor of the day, and so on. If you ever start to outgrow your current tech stack, you can always add more complexity later, but you cannot always simplify an overly complex tech stack.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;minimize-your-dependencies&#34;&gt;Minimize your dependencies&lt;/h4&gt;
&lt;p&gt;In today&amp;rsquo;s highly connected world, software applications don&amp;rsquo;t exist in a vacuum. There can be a vast number of dependencies as far as the eye can see. Just looking at the dependency tree of a typical Node.js application can reveal tens of primary dependencies, with each dependency having its own dependencies and those dependencies having their own, ad infinitum. If you count them all, you can easily end up with hundreds or even thousands of dependencies in total, and this is just the code side. The dependency trees on the server side can be even deeper if you go all the way down to kernel level.&lt;/p&gt;
&lt;p&gt;With so many dependencies, it&amp;rsquo;s a miracle that anything works. As software developers, we really are standing on the shoulders of millions of software developers.&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s next to impossible to have control over your entire dependency tree, the only thing that you can realistically control is your primary dependencies, and it&amp;rsquo;s a good idea to keep them as few as possible. Statistically speaking, the more dependencies you have, the greater the chances of something going wrong.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;avoid-scope-creep&#34;&gt;Avoid scope creep&lt;/h4&gt;
&lt;p&gt;Software is infinitely malleable, and there&amp;rsquo;s always the temptation of adding one more feature. Don&amp;rsquo;t, under any circumstances, accept change requests if the previously agreed-upon timeline for development would stay the same.&lt;/p&gt;
&lt;p&gt;Many software developers are eternal optimists, a good trait that&amp;rsquo;s necessary to deal with a seemingly unending string of technical challenges, but it can also be our undoing. Software development is a complex process, and there are oftentimes previously unrealized technical issues that need to be addressed before implementing a feature. There&amp;rsquo;s a good chance that a seemingly minor change may require more time than originally thought.&lt;/p&gt;
&lt;p&gt;Finally, don&amp;rsquo;t fall into the trap of non-technical people trying to convince you with shaky arguments like &amp;ldquo;How hard could it be? App X already has that feature.&amp;rdquo; The truth is they don&amp;rsquo;t have the technical background to speculate about the difficulty of technical problems. However, you still need to explain the technical aspects to them in plain language. A curt &amp;ldquo;No, that&amp;rsquo;s not possible.&amp;rdquo; won&amp;rsquo;t suffice.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;prioritize-by-taking-business-realities-into-account&#34;&gt;Prioritize by taking business realities into account&lt;/h4&gt;
&lt;p&gt;There are many methods to choose for prioritizing the items in your roadmap. It&amp;rsquo;s not critically important which one you use, but you should pick one and stick with it. I personally like &lt;a href=&#34;https://en.wikipedia.org/wiki/Cost_of_delay&#34;&gt;Cost of Delay&lt;/a&gt; because it focuses on the financial impact of feature requests &amp;ndash; a seemingly important feature may easily turn out to be not so important for your company from a financial perspective.&lt;/p&gt;
&lt;p&gt;Jim Rohn said, &amp;ldquo;Time is our most valuable asset, yet we tend to waste it, kill it, and spend it rather than invest it.&amp;rdquo; Companies need a steady cash flow to stay healthy, and implementing software features that don&amp;rsquo;t bring in revenue is not only a waste of time but also incurs a significant opportunity cost.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;build-prototypes-before-making-risky-decisions&#34;&gt;Build prototypes before making risky decisions&lt;/h4&gt;
&lt;p&gt;Some of the decisions you make may involve a lot of unknowns that need to be explored before making a final decision. In cases like that, it pays to do some exploratory work before fully committing to a plan of action.&lt;/p&gt;
&lt;p&gt;For example, digitizing energy bills is fraught with risks. It&amp;rsquo;s all too easy to get the numbers wrong. We weren&amp;rsquo;t comfortable with the potentially high error rate. So, we explored various technologies. OCR was the obvious choice, but it performed poorly in our experiments.&lt;/p&gt;
&lt;p&gt;Switching to large language models improved reliability significantly, but if we hadn&amp;rsquo;t tried exploring different technologies and built prototypes, we might have made the error of committing to using OCR, costing us a great deal of time and money.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;work-in-cross-functional-teams&#34;&gt;Work in cross-functional teams&lt;/h4&gt;
&lt;p&gt;A successful software application isn&amp;rsquo;t just written &amp;ndash; it&amp;rsquo;s the joint effort of a cross-functional team of product managers, UX designers, software developers, and QA engineers.&lt;/p&gt;
&lt;p&gt;If a UX designer creates a screen design that would take twice as long to implement as a more traditional design, the software developers can offer some revisions for a faster turnaround as long as the original design isn&amp;rsquo;t compromised much.&lt;/p&gt;
&lt;p&gt;Similarly, if the scope of a feature as outlined by the product manager is too complex to implement in a reasonable amount of time, the scope can be streamlined according to the feedback of the rest of the team.&lt;/p&gt;
&lt;p&gt;Finally, if the software developers say they need to pay off some of the accumulated technical depth by doing some refactoring work, the development schedule can be revised to allow time for the refactoring.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;trust-your-team&#34;&gt;Trust your team&lt;/h4&gt;
&lt;p&gt;This is the most important principle by far. If you can&amp;rsquo;t trust your team to make the necessary day-to-day technical decisions, then you will be quickly overwhelmed and your team will likely be demoralized as well. As the Russian proverb says, &amp;ldquo;Trust, but verify&amp;rdquo;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Economics of Bugs</title>
      <link>https://lackofimagination.org/2024/02/economics-of-bugs/</link>
      <pubDate>Tue, 20 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/02/economics-of-bugs/</guid>
      <description>&lt;p&gt;Software development would be a lot easier without so many bugs. In a typical software application, there&amp;rsquo;s a never-ending stream of bugs as far as the eye can see. Some of the bugs make users&amp;rsquo; lives miserable, some of them are annoying but can be dealt with workarounds, and the rest are mostly minor inconveniences. If that sounded depressing, I&amp;rsquo;m sorry, that wasn&amp;rsquo;t my intention, I&amp;rsquo;m only trying to survey the current software quality landscape not only as a software developer but a user.&lt;/p&gt;
&lt;p&gt;When writing code, unless there&amp;rsquo;s the possibility of putting the lives of people in danger or losing a lot of money, software developers are often incentivized to produce code quickly since time to market is almost always deemed to be critical by the powers that be. So, developers write code that works anywhere between 0.1% of the time (&lt;a href=&#34;https://lackofimagination.org/2024/02/works-on-my-machine/&#34;&gt;works on my machine&lt;/a&gt;) to 99.9% of the time (mostly bug-free). Given that the actual application code software developers write is only a fraction of the total code that is running on a computer (there are many dependencies such as the operating system, databases, third-party libraries, web services, and so on), it&amp;rsquo;s next to impossible to expect 100% bug-free applications. But, we often fall far short of the hypothetical 100%.&lt;/p&gt;
&lt;p&gt;To me, nothing is more frustrating than a critical bug in a feature you rely on, which had been working all this time, but the developers managed to break it in the latest update. How can they mess it up and release something so obviously broken? Aren&amp;rsquo;t the developers using their own software? The answer is&amp;hellip; no, not really, at least not the way you are using it. They&amp;rsquo;re not the pilots flying the plane; they&amp;rsquo;re the engineers building it. In fact, as a user, you probably know more about the intricacies of your favorite feature than its developers do.&lt;/p&gt;
&lt;p&gt;Since bugs are a part of life, you might be prepared to forgive a critical bug if they fix the bug quickly, but what if they don&amp;rsquo;t? A bug might only affect a small subset of users, so the developers might have a hard time reproducing it. Still, isn&amp;rsquo;t the developer&amp;rsquo;s job to anticipate all the things that could go wrong? The answer is, once again, not really. In an ideal world, where money is no object and you can spend as much time as you need to write high quality code, it may be possible to produce almost perfect software. But, we&amp;rsquo;re not living in an ideal world. If there aren&amp;rsquo;t many users who are willing to pay a premium for highly reliable software, then the financial incentive isn&amp;rsquo;t obviously there.&lt;/p&gt;
&lt;p&gt;Can we still write mostly bug-free software even when the immediate financial concerns aren&amp;rsquo;t in our favor? After all, producing high quality software could, in the long run, turn out to be a significant competitive advantage since developers will be able to spend more time in improving their software than fixing bugs, which brings us to the use of the scientific method in software development, or more often than not, the lack of it.&lt;/p&gt;
&lt;p&gt;The scientific method is about forming a falsifiable hypothesis and trying to refute it by finding at least one conflicting condition. In software development, however, a more optimistic route is usually taken. Yes, the code is tested to see if it works in certain conditions, but those conditions usually entail the &lt;a href=&#34;https://en.wikipedia.org/wiki/Happy_path&#34;&gt;happy path&lt;/a&gt;. Testing edge cases, the source of most bugs, is often a lower priority.&lt;/p&gt;
&lt;p&gt;So, are we doing it wrong? Shouldn&amp;rsquo;t we pay more attention to finding and testing edge cases? The answer again lies in economics. The proper handling of a large number of edge cases means doing a lot of work that isn&amp;rsquo;t always appreciated by users who need a feature yesterday. So, what do software developers under time pressure do? They enclose the happy path code in a try-catch block, and return an &amp;ldquo;Unknown error&amp;rdquo; message if something goes wrong. You now know where those cryptic error messages come from.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s easy to criticize software that is full of bugs, but it might not make much financial sense to produce highly reliable software with only a few features if most users in your market prefer software that, despite having more bugs, offers twice as many.&lt;/p&gt;
&lt;p&gt;If I&amp;rsquo;ve come across as someone defending mediocrity in software development just because the financial incentives aren&amp;rsquo;t there, that&amp;rsquo;s not what I meant. No software developer who takes pride in their work, by definition, wants to produce less than great software, but good engineering is a balancing game. You are usually given a time budget, and are expected to do your best within that budget. Good engineers know how to negotiate rather than doing blindly what they&amp;rsquo;ve been told. They might offer cutting or simplifying features in order not to spread too thin, and good management listens to what they have to say.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Works On My Machine</title>
      <link>https://lackofimagination.org/2024/02/works-on-my-machine/</link>
      <pubDate>Wed, 07 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/02/works-on-my-machine/</guid>
      <description>&lt;p&gt;&lt;strong&gt;But it works on my machine!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you work with software developers, especially those with less experience, it&amp;rsquo;s virtually guaranteed to hear one of them utter a version of the above expression when someone presents them with a situation they think is impossible (or more likely, improbable) to happen in the program they&amp;rsquo;ve written. Now, the great thing about software is that it&amp;rsquo;s &lt;em&gt;mostly&lt;/em&gt; deterministic &amp;ndash; if something strange happens, it&amp;rsquo;s very likely caused by a bug in the code rather than some unexplained cosmic coincidence. I&amp;rsquo;ve written about &lt;a href=&#34;https://lackofimagination.org/2023/11/debugging-is-programming/&#34;&gt;debugging in general&lt;/a&gt; before, so I&amp;rsquo;d like to focus on a set of bugs specifically caused by &lt;em&gt;subsystems&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware/OS/Compiler&lt;/strong&gt;&lt;br&gt;
Programs don&amp;rsquo;t run in isolation. They depend on many subsystems to function. Obviously, you need a computer and a network connection. Your program also needs to be converted into a form the hardware/operating system combo can run, through the use of a compiler or interpreter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Libraries&lt;/strong&gt;&lt;br&gt;
Next come the libraries. Nowadays, it&amp;rsquo;s rare for anyone to write code entirely from scratch, thanks to the vast array of freely available libraries that cover tasks from the simplest to the most complex. Just as your program has a version, the libraries it depends on also have their own versions. Fortunately, there are useful programs called package managers that automatically download and install the correct library versions your program needs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Filesystems/Databases&lt;/strong&gt;&lt;br&gt;
If people use your program to store data, you will need to keep it somewhere. Popular choices are filesystems and databases that can be located on the same machine as your program or on another machine accessible over the network. In the case of relational databases, schemas are used to define the structure of the data stored.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remote Services&lt;/strong&gt;&lt;br&gt;
In the interconnected world we live in, your program may need to interface with remote services. These services can either be internal or third-party. Remote service availability and the expected behavior of each service are important considerations since you may not have direct control over them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Web/Mobile/Desktop&lt;/strong&gt;&lt;br&gt;
Most users access programs via web browsers or mobile devices. Yes, there are still quite a few widely used desktop (or should we call them laptop?) applications, but they are in the minority. Unlike desktop applications, web applications have less control over the environments they run in &amp;ndash; different web browsers or even different versions of a particular browser may behave differently. As for mobile devices, they have widely varying capabilities that can be difficult to manage. This is especially true in the relatively fragmented Android world.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Program Configuration&lt;/strong&gt;&lt;br&gt;
Finally, many programs have configuration options that can be used to change their behavior, sometimes quite markedly, and these options may even interact with each other in unexpected ways.&lt;/p&gt;
&lt;p&gt;Aside from the subsystems mentioned so far, there are other subsystems specific to certain kinds of programs such as games and embedded applications, but I think it&amp;rsquo;s safe to say that we&amp;rsquo;ve covered the most common ones. Statistically speaking, the more the subsystems your program relies on, the greater the chances of something going wrong.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Why does a feature only work on a developer&amp;rsquo;s machine?&lt;/strong&gt;&lt;br&gt;
Unless the developer&amp;rsquo;s machine has supernatural capabilities, the reason is most likely because one or more subsystems on their machine behave differently than the equivalent subsystems on other machines.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automated Deploys&lt;/strong&gt;&lt;br&gt;
First, let&amp;rsquo;s get the obvious out of the way. Do you have an automated deploy method? Do you use a deploy script, Docker image, or some other automated mechanism to deploy software, which has the added benefit of easily rolling back to a previous deployment when something goes wrong? If not, you will probably waste a lot of time chasing bugs that could easily be prevented by automated deploys in the first place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Package Managers&lt;/strong&gt;&lt;br&gt;
Using a package manager is essential. Otherwise, it&amp;rsquo;s too easy to lose track of which library versions your program is compatible with. When you use the wrong version of a library, your program may not fail right away; it may even appear to be working fine, but things can start to go wrong in subtle ways, which makes it hard to track the root cause of the bugs you will inevitably encounter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database Snapshots&lt;/strong&gt;&lt;br&gt;
As the primary data storage system used in many applications, the database probably holds the distinction of causing the most &amp;ldquo;works on my machine&amp;rdquo; bugs. Data stored in databases are living things. They change over time, sometimes a lot, but many developers tend to use outdated or unrealistic datasets during development and testing.&lt;/p&gt;
&lt;p&gt;It greatly helps developers track down hard-to-reproduce bugs if you provide them with recent database backups from the production environment, after anonymizing and encrypting sensitive data, of course. If backups are too large, creating a remote database environment that developers can connect to is a good idea. Using a filesystem like &lt;a href=&#34;https://lackofimagination.org/2022/04/our-experience-with-postgresql-on-zfs/&#34;&gt;ZFS&lt;/a&gt; makes it incredibly easy to spin up a database from a past snapshot. The database is ready in seconds, no matter how large it is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message Queues&lt;/strong&gt;&lt;br&gt;
When remote services work as expected, it&amp;rsquo;s great; when they don&amp;rsquo;t, all sorts of strange things can happen. It&amp;rsquo;s tempting to assume that remote services will respond back quickly, but in the production environment, they can get overloaded and may respond late or not at all. Also, you wouldn&amp;rsquo;t really want to accidentally DoS yourself by repeatedly making requests to an already slow service, making things even slower. Sensible timeouts and &lt;a href=&#34;https://en.wikipedia.org/wiki/Exponential_backoff&#34;&gt;exponential backoff&lt;/a&gt; are often used to relieve the load on remote services if they fail to respond in a timely manner. Finally, whenever possible, it&amp;rsquo;s recommended not to query remote services directly, and use a message queue as a buffer instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing with Multiple Browsers/Devices&lt;/strong&gt;&lt;br&gt;
It&amp;rsquo;s not always possible to make a web app work exactly the same across all major browsers, but the differences should be small enough not to warrant attention. Whenever you develop a new feature, it pays to test with all major browsers including mobile browsers if your application is responsive. As for mobile apps, screen sizes are more or less standard these days, but it&amp;rsquo;s still easy to fall in the trap of assuming everyone has big screens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sensible Defaults&lt;/strong&gt;&lt;br&gt;
Not everyone can keep in mind what each configuration option does especially when you have hundreds of them. So, it&amp;rsquo;s important to have sensible defaults, or really strange things can happen like the &lt;a href=&#34;https://www.ibiblio.org/harris/500milemail.html&#34;&gt;inability to send e-mail farther than 500 miles&lt;/a&gt;, as one university system administrator found out.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Decoding Complex-Sounding Programming Terms with Examples</title>
      <link>https://lackofimagination.org/2024/01/decoding-complex-sounding-programming-terms-with-examples/</link>
      <pubDate>Wed, 24 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/01/decoding-complex-sounding-programming-terms-with-examples/</guid>
      <description>&lt;p&gt;Richard Feynman, the Nobel Prize-winning physicist, developed an interest in biology while he was a graduate student at Princeton. He started attending a course in cell physiology, which required him to report on papers assigned to him. One such paper was about measuring the voltages generated by the nerves in cats:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I began to read the paper. It kept talking about extensors and flexors, the gastrocnemius muscle, and so on. This and that muscle were named, but I hadn&amp;rsquo;t the foggiest idea of where they were located in relation to the nerves or to the cat. So I went to the librarian in the biology section and asked her if she could find me a map of the cat.&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;ldquo;A map of the cat, sir?&amp;rdquo; she asked, horrified. &amp;ldquo;You mean a zoological chart!&amp;rdquo; From then on there were rumors about some dumb biology graduate student who was looking for a &amp;ldquo;map of the cat.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the software world, we don&amp;rsquo;t have as many complicated terms as there are in biology, but we aren&amp;rsquo;t completely immune to giving complex-sounding names to what should be simple concepts &amp;ndash; some of us even enjoy getting pedantic about them. Here are some of my favorites, with examples in JavaScript:&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;dependency-injection&#34;&gt;Dependency Injection&lt;/h4&gt;
&lt;p&gt;When you pass objects as arguments to an object, it&amp;rsquo;s called dependency injection. Yes, that&amp;rsquo;s all there is to it.&lt;/p&gt;
&lt;p&gt;The following constructor function can be used to generate UserService objects with a logger dependency. The dependency can be &lt;em&gt;injected&lt;/em&gt; from the outside, which allows the use of different logging libraries.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserService&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;logger&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;logger&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;info&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Created User:&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;myUserService&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserService&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;newUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;myUserService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Katniss&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Everdeen&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// [INFO] Created User: Katniss Everdeen
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;factory-method&#34;&gt;Factory Method&lt;/h4&gt;
&lt;p&gt;Despite the lofty name, a factory method just returns an object.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; () =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;newUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Asuka&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Langley&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;newUser&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Prints Asuka Langley
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;higher-order-function&#34;&gt;Higher-Order Function&lt;/h4&gt;
&lt;p&gt;A function that returns a function or takes other functions as arguments is called a higher-order function.&lt;/p&gt;
&lt;p&gt;Most popular programming languages natively support higher-order functions, or added support for them.&lt;/p&gt;
&lt;p&gt;In the code below, &lt;code&gt;_.pickBy&lt;/code&gt; passes each property in the collection to the provided functions (&lt;code&gt;_.isNumber&lt;/code&gt; or &lt;code&gt;_.isString&lt;/code&gt;), and if those functions return true, it &amp;ldquo;picks&amp;rdquo; the corresponding properties from the collection.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lodash&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;collection&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;pickBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;collection&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isNumber&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// { &amp;#39;a&amp;#39;: 1, &amp;#39;c&amp;#39;: 3 }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;pickBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;collection&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isString&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// { b: &amp;#39;2&amp;#39; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;idempotence&#34;&gt;Idempotence&lt;/h4&gt;
&lt;p&gt;A function is called &amp;ldquo;idempotent&amp;rdquo; if multiple calls to the function have the same effect on the program state as a single call. In other words, no matter how many times you call the function with the same arguments, the program state will not change after the first call.&lt;/p&gt;
&lt;p&gt;The following function updates the status of a message with the given id in the database. The program state will not change after the first call. So, this function is idempotent.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// (...)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;updateMessageStatus&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;update&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; }).&lt;span style=&#34;color:#a6e22e&#34;&gt;where&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;updateMessageStatus&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;read&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;updateMessageStatus&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;read&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;updateMessageStatus&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;read&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// The message #1&amp;#39;s status will always be &amp;#39;read&amp;#39;.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;immutability&#34;&gt;Immutability&lt;/h4&gt;
&lt;p&gt;All variable assignments are final. Once you assign a value to a variable, you aren&amp;rsquo;t allowed to change it in any way.&lt;/p&gt;
&lt;p&gt;In the example below, JavaScript&amp;rsquo;s native array &lt;code&gt;sort&lt;/code&gt; method sorts an array in place, replacing the values of the original array. Lodash&amp;rsquo;s &lt;code&gt;sortBy&lt;/code&gt; method, on the other hand, doesn&amp;rsquo;t &lt;em&gt;mutate&lt;/em&gt; the original array, and returns a new array instead. It&amp;rsquo;s &lt;a href=&#34;https://en.wikipedia.org/wiki/Immutable_object&#34;&gt;good programming practice&lt;/a&gt; to avoid changing variable values whenever you can even if the programming language you use only has partial immutability support like JavaScript.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lodash&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mutableElements&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;earth&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;water&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;air&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fire&amp;#39;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;mutableElements&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sort&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;mutableElements&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// [ &amp;#39;air&amp;#39;, &amp;#39;earth&amp;#39;, &amp;#39;fire&amp;#39;, &amp;#39;water&amp;#39; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;immutableElements&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;earth&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;water&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;air&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fire&amp;#39;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;sortedImmutableElements&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sortBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;immutableElements&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;immutableElements&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sortedImmutableElements&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// [ &amp;#39;earth&amp;#39;, &amp;#39;water&amp;#39;, &amp;#39;air&amp;#39;, &amp;#39;fire&amp;#39; ] [ &amp;#39;air&amp;#39;, &amp;#39;earth&amp;#39;, &amp;#39;fire&amp;#39;, &amp;#39;water&amp;#39; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;memoization&#34;&gt;Memoization&lt;/h4&gt;
&lt;p&gt;As a form of caching, a memoized function &amp;ldquo;remembers&amp;rdquo; the result it has previously returned for a given set of inputs. Subsequent calls with the same inputs return the remembered result rather than recalculating it.&lt;/p&gt;
&lt;p&gt;Lodash has a convenient &lt;code&gt;memoize&lt;/code&gt; method that can be used to turn any function into a &lt;em&gt;memoized&lt;/em&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lodash&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// (...)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;memoizedSlowFunction&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;memoize&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;slowFunction&lt;/span&gt;, (...&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JSON&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringify&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s all for now. Let me know what your favorite complex-sounding yet simple programming terms are.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>On Unix Philosophy</title>
      <link>https://lackofimagination.org/2024/01/on-unix-philosophy/</link>
      <pubDate>Sun, 14 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/01/on-unix-philosophy/</guid>
      <description>&lt;p&gt;I will never forget the time I discovered how web pages were made. The year was 1995. I was using Windows 3.1 like most everyone, and it didn&amp;rsquo;t even support the TCP/IP network stack out of the box. I had to install a program called Trumpet Winsock and a hot new web browser called Netscape to connect to the Internet.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/welcome_to_netscape.gif&#34;  alt=&#34;Netscape Navigator 1.2 on Windows 3.1 - Source: Wikipedia&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Netscape Navigator 1.2 on Windows 3.1 - Source: Wikipedia&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;In Netscape, there was an option called &amp;ldquo;Source&amp;hellip;&amp;rdquo; under the View menu, and when I clicked it, I was half expecting to see some sort of cryptic binary file format that would require special software to generate. What I got instead was the actual text of the web page I was viewing, with a bunch of HTML tags thrown in for styling. That was a revelation &amp;ndash; with a simple text editor, anyone could create a web page!&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t know at the time, but it was part of the Unix philosophy to use text streams to exchange data between programs. Had Microsoft or some other company managed to invent the web instead of Tim Berners-Lee, I bet they would have opted for a proprietary binary format for web page delivery, as it was customary in the non-Unix world.&lt;/p&gt;
&lt;p&gt;In those early days, surfing the web was an amazing experience for me. I had, for the first time in my life, instant access to people anywhere in the world. There was little content back then of course, and connection speeds were very slow. &amp;ldquo;Image-heavy&amp;rdquo; web pages could easily take several minutes to download on my 0.014 megabit landline phone/Internet connection.&lt;/p&gt;
&lt;p&gt;These days, we have incomparably fast Internet speeds &amp;ndash; multi-megabyte web pages download in seconds, and a lot of people watch TV online thanks to the wide availability of on-demand TV series and movies in high resolution. To top it off, all these feats are possible with no wires in sight. How far we&amp;rsquo;ve come, and yet one thing hasn&amp;rsquo;t changed &amp;ndash; web pages are still delivered in plain text.&lt;/p&gt;
&lt;p&gt;Text is the universal interface all humans can understand. You can use literally thousands of different programs to view and process text files. Binary files, on the other hand, require specialized programs that greatly reduce their utility. Granted, not everything can be a text file. Images and sound recordings are better stored in binary, but there&amp;rsquo;s usually no good reason to use proprietary binary file formats when a simple text file is enough, especially when large text files can easily be compressed on the fly.&lt;/p&gt;
&lt;p&gt;Many software developers fall in the trap of writing a program for every problem they encounter, even when there are better alternatives to achieve the same outcome. Modern operating systems based on Unix such as Linux and MacOS come with a large number of built-in Command-Line-Interface (CLI) programs that can be chained together to solve seemingly complex tasks with ease, often with nothing more than one-liners. But first, let&amp;rsquo;s hear what Master Foo has to say about the subject :)&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4 id=&#34;master-foo-and-the-ten-thousand-lines&#34;&gt;Master Foo and the Ten Thousand Lines&lt;/h4&gt;
&lt;p&gt;Master Foo once said to a visiting programmer: &amp;ldquo;There is more Unix-nature in one line of shell script than there is in ten thousand lines of C&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The programmer, who was very proud of his mastery of C, said: &amp;ldquo;How can this be? C is the language in which the very kernel of Unix is implemented!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Master Foo replied: &amp;ldquo;That is so. Nevertheless, there is more Unix-nature in one line of shell script than there is in ten thousand lines of C&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The programmer grew distressed. &amp;ldquo;But through the C language we experience the enlightenment of the Patriarch Ritchie! We become as one with the operating system and the machine, reaping matchless performance!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Master Foo replied: &amp;ldquo;All that you say is true. But there is still more Unix-nature in one line of shell script than there is in ten thousand lines of C&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The programmer scoffed at Master Foo and rose to depart. But Master Foo nodded to his student Nubi, who wrote a line of shell script on a nearby whiteboard, and said: &amp;ldquo;Master programmer, consider this pipeline. Implemented in pure C, would it not span ten thousand lines?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The programmer muttered through his beard, contemplating what Nubi had written. Finally he agreed that it was so.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;And how many hours would you require to implement and debug that C program?&amp;rdquo; asked Nubi.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Many&amp;rdquo;, admitted the visiting programmer. &amp;ldquo;But only a fool would spend the time to do that when so many more worthy tasks await him&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;And who better understands the Unix-nature?&amp;rdquo; Master Foo asked. &amp;ldquo;Is it he who writes the ten thousand lines, or he who, perceiving the emptiness of the task, gains merit by not coding?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Upon hearing this, the programmer was enlightened.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&amp;rsquo;t know which script Nubi wrote on the whiteboard, but the following one-liner, when run in Linux or MacOS returns the 5 most frequently used words in a file (in this case LICENSE), and prints out a sorted list of those words along with their frequencies:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat LICENSE | egrep -o &amp;#34;\w+&amp;#34; | awk &amp;#39;{print tolower($0)}&amp;#39; | sort | uniq -c | sort -rn | head -n5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  14 the
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   9 software
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   9 or
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   8 to
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   8 of
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Some of you might recognize the script I wrote above as the somewhat modernized version of &lt;a href=&#34;http://www.leancrew.com/all-this/2011/12/more-shell-less-egg/&#34;&gt;Doug McIlroy&amp;rsquo;s version of Donald Knuth&amp;rsquo;s 10+ page Pascal program&lt;/a&gt;. I&amp;rsquo;m certainly no Donald Knuth, but I think it&amp;rsquo;s correct to say that even the most brilliant minds may sometimes make things more complicated than they should be.&lt;/p&gt;
&lt;p&gt;The power of shell scripts is a direct manifestation of the Unix Philosophy, as succinctly summarized by Doug McIlroy, the inventor of Unix pipes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write programs that do one thing and do it well.&lt;/li&gt;
&lt;li&gt;Write programs to work together.&lt;/li&gt;
&lt;li&gt;Write programs to handle text streams, because that is a universal interface.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As software developers, what can we learn from the Unix philosophy? Isaac Z. Schumpeter, the creator of npm, suggests the following principles in the context of Node.js modules, but I think they are general enough to cover software modules written in other programming languages as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write modules that do one thing well. Write a new module rather than complicate an old one.&lt;/li&gt;
&lt;li&gt;Write modules that encourage composition rather than extension.&lt;/li&gt;
&lt;li&gt;Write modules that handle data streams, because that is the universal interface.&lt;/li&gt;
&lt;li&gt;Write modules that are agnostic about the source of their input or the destination of their output.&lt;/li&gt;
&lt;li&gt;Write modules that solve a problem you know, so you can learn about the ones you don&amp;rsquo;t.&lt;/li&gt;
&lt;li&gt;Write modules that are small. Iterate quickly. Refactor ruthlessly. Rewrite bravely.&lt;/li&gt;
&lt;li&gt;Write modules quickly, to meet your needs, with just a few tests for compliance. Avoid extensive specifications. Add a test for each bug you fix.&lt;/li&gt;
&lt;li&gt;Write modules for publication, even if you only use them privately. You will appreciate documentation in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unix has been around in one form or another for over half a century now, and the philosophy behind it remains as relevant today as when it first emerged, not just for operating systems but for software development in general. While combining different modules in software development isn&amp;rsquo;t quite as straightforward as snapping Lego bricks together, the Unix philosophy is guiding us towards this goal, one brick at a time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Worth a Read:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.catb.org/esr/writings/taoup/html/ch01s06.html&#34;&gt;Basics of the Unix Philosophy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.izs.me/2013/04/unix-philosophy-and-nodejs/&#34;&gt;Unix Philosophy and Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.catb.org/~esr/writings/unix-koans/&#34;&gt;Rootless Root: The Unix Koans of Master Foo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>DIY Dual Column Speaker Stands</title>
      <link>https://lackofimagination.org/2024/01/diy-dual-column-speaker-stands/</link>
      <pubDate>Sun, 07 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2024/01/diy-dual-column-speaker-stands/</guid>
      <description>&lt;p&gt;I was looking to buy speaker stands for my KEF LS50 Metas and the &lt;a href=&#34;https://lackofimagination.org/2023/11/making-a-kef-ls50-clone/&#34;&gt;DIY KEF LS50 Clone&lt;/a&gt; I use as my center channel speaker. KEF&amp;rsquo;s matching extruded aluminum S2 stands were a natural choice. Unfortunately, they were quite expensive, and I&amp;rsquo;m not really a fan of single column stands anyway, so I decided to build a few dual column stands myself.&lt;/p&gt;
&lt;p&gt;I picked 8 mm MDF for the sides of the columns, 12 mm MDF for the top plate, and 18 mm MDF for the bottom plate. The columns will be hollow, so they can be filled with sand to make the stands acoustically more inert. Finally, the top and bottom sides of the columns will be made of 15 mm scrap birch plywood I had on hand.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy-speaker-stands/diy_speaker_stands_pieces_cut.jpg&#34;  alt=&#34;Pieces cut for one speaker stand.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Pieces cut for one speaker stand.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The stands will be approximately 65 cm (25.6&amp;quot;) tall, including the spikes. Each column will be 5 cm wide and 15 cm deep. The distance between the columns will be 2 cm. After cutting the pieces, I dry-fitted the columns to make sure the pieces fit each other, then glued the pieces together with wood glue, and held everything in place using clamps.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy-speaker-stands/diy_speaker_stands_dry_fit.jpg&#34;  alt=&#34;Column dry fit.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Column dry fit.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;After the glue dried, I used my router table to flush trim all edges and applied a 3/8&amp;quot; (~9.5 mm) roundover to the columns for a softer look. Next, I drilled holes into the top and bottom sides of the columns and put in M6 threaded inserts. This will allow me to secure the top and bottom plates in place using M6 countersunk socket head screws.&lt;/p&gt;
&lt;p&gt;I then performed a test assembly to ensure that the holes lined up. Notice the different top plate sizes in the picture below. The stand on the left uses a smaller top plate for my KEF LS50 Metas and the one on the right is for my bigger KEF LS50 clone.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy-speaker-stands/diy_speaker_stands_different_top_plates.jpg&#34;  alt=&#34;Test assembly of my DIY speaker stands with different top plates.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Test assembly of my DIY speaker stands with different top plates.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;After sanding, the stands were sprayed with several coats of black paint.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy-speaker-stands/diy_speaker_stands_painted.jpg&#34;  alt=&#34;Stands painted black.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Stands painted black.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;After the paint dried and cured, I assembled the stands and screwed brass spikes into the M8 threaded inserts I had placed in the corners of the bottom plate. Rubber feet could be used in place of spikes on hardwood/concrete floors.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy-speaker-stands/diy_speaker_stands_bottom_plate_with_spikes.jpg&#34;  alt=&#34;Brass spikes.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Brass spikes.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Each stand, fully assembled, weighs roughly 4.5 KG (~10 pounds), incidentally about the same weight as empty KEF S2 stands. I haven&amp;rsquo;t filled them with sand yet, but they should weigh about twice as much when fully filled.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy-speaker-stands/diy_speaker_stand_painted_black.jpg&#34;  alt=&#34;Stands fully assembled.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Stands fully assembled.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I think the stands turned out well. There&amp;rsquo;s a little too much orange peel in the paint for my taste, and there are a few minor blemishes that I might touch up in the future, but you need to get really close to see them, which is next to impossible in my in home theater with all the dark surfaces around.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>One Function Per Line</title>
      <link>https://lackofimagination.org/2023/12/one-function-per-line/</link>
      <pubDate>Wed, 20 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/12/one-function-per-line/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s often said that reading code is harder than writing it, but is it really? If that were the case for books, everyone would be writing their own rather than reading the books others wrote. But, a page of code isn&amp;rsquo;t equivalent to a page of text. Code, by nature, is denser. To effectively explain what a page of code does may easily take several pages, and in some cases a lot more.&lt;/p&gt;
&lt;p&gt;Is it possible to make code easier to read than to write? After all, code is read more often than it is written when multiple developers work on the same codebase. If developers spend less time to understand how the existing code works, they can start writing code faster.&lt;/p&gt;
&lt;p&gt;In his book Code Complete, Steve McConnell describes a process called &lt;em&gt;pseudocode programming&lt;/em&gt; that starts with writing pseudocode where each line, written in English, describes a specific operation in relatively high level. You then turn the pseudocode into comments, and finally fill in the code below each comment. Here is an example in JavaScript:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Validate input fields, and return error if validation fails
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Check if the password conforms to the password policy, if not return error
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Check the user table to see if email is available, if not return error
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Hash user password by using a strong hashing algorithm
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Create a new record in the user table, return user&amp;#39;s id on success or return error
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Though it lacks a few details, I don&amp;rsquo;t think any developer would have a problem understanding the above pseudocode. Comments also come handy in AI powered code generation tools to auto generate some of the code.&lt;/p&gt;
&lt;p&gt;Can we go one step further, and actually write code that looks like pseudocode? By using a variation of the Chain of Responsibility pattern, it&amp;rsquo;s possible to write code that appears remarkably similar to pseudocode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chain&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fn-one&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// (...)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chain&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		() =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;validateInput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		() =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isPasswordValid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		() =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isEmailAvailable&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		() =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		(&lt;span style=&#34;color:#a6e22e&#34;&gt;hashedPassword&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;hashedPassword&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For chaining functions, I used &lt;a href=&#34;https://github.com/aycangulez/fn-one&#34;&gt;fn-one&lt;/a&gt;, a tiny library I wrote, but there are quite a few function chaining libraries out there. It doesn&amp;rsquo;t matter which one you use, what matters is, whenever possible, to call one function per line. This style of coding may require you to write a lot of small functions, but the code can become significantly easier to follow as a result.&lt;/p&gt;
&lt;p&gt;When chaining functions, each function&amp;rsquo;s output is passed to the next function in line. If a function returns &lt;em&gt;false&lt;/em&gt; or throws an exception, the chain breaks. There are no explicit control structures such as if-else statements and loops that clutter the code. They are hidden inside lower-level functions where they belong.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Writing Robust Code via Idempotence</title>
      <link>https://lackofimagination.org/2023/12/writing-robust-code-via-idempotence/</link>
      <pubDate>Thu, 14 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/12/writing-robust-code-via-idempotence/</guid>
      <description>&lt;p&gt;What happens when a function call fails? Can you retry without causing any unintended side effects? If the answer is no, then your function is probably not robust enough.&lt;/p&gt;
&lt;p&gt;Here is a scenario that is very likely happening to someone somewhere right at this moment. Let&amp;rsquo;s call this person Midori. Midori has been eyeing a pair of headphones, fancy noise cancelling ones, no less, and as soon as there&amp;rsquo;s a sale on them, she adds the phones to her shopping cart, and with a touch of excitement, clicks the Order button. A few seconds pass, but nothing happens, and then she waits some more, but still nothing. Has the order gone through? Does she need to order again?&lt;/p&gt;
&lt;p&gt;There can be different reasons as to why the order page might get stuck &amp;ndash; Midori&amp;rsquo;s Internet connection might have dropped right before or after she clicked the Order button, or maybe her connection was OK, but the server was busy.&lt;/p&gt;
&lt;p&gt;Midori goes back to her cart and notices the headphones are still there. Can she safely assume that the order hasn&amp;rsquo;t gone through? Maybe the cart hasn&amp;rsquo;t been updated to reflect the order, and is showing outdated information.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A function is called &lt;em&gt;&amp;ldquo;idempotent&amp;rdquo;&lt;/em&gt; if multiple calls to the function have the same effect on the program state as a single call. In other words, no matter how many times you call the function with the same arguments, the program state will not change after the first call.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In Midori&amp;rsquo;s case, if the server has received the order, but has no way to respond due to the dropped connection, a second order won&amp;rsquo;t be executed if the function responsible for the ordering process was designed to be idempotent. For example, if the shopping web site sends a unique &amp;ldquo;idempotency&amp;rdquo; key to the server for each order, the server will realize that it has already executed the order, and won&amp;rsquo;t do anything.&lt;/p&gt;
&lt;p&gt;Idempotency is often discussed in the context of HTTP methods. According to the &lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc7231#section-4.2.2&#34;&gt;HTTP 1.1 specification&lt;/a&gt;, in addition to the normally idempotent GET method, the methods PUT (update), and DELETE should be implemented in an idempotent manner, but this is only possible if unique identifiers are used. For example, the function handling the following request&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;PUT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;messages&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;123&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;read&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;can be made idempotent because &lt;em&gt;only&lt;/em&gt; the message with the id 123 will be updated. However, if your API supports updating messages using non-unique identifiers, such as a date range, it may not be truly idempotent because the same date range can update different messages in the future.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resuming Interrupted Operations&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Idempotent functions aren&amp;rsquo;t just about limiting state changes &amp;ndash; they may also allow resuming interrupted operations. For example, if you are synchronizing files across computers, and the process is interrupted, a second call can start where the previous call left off. Long-running batch SQL jobs that process millions of records are also prime candidates for idempotency. If something goes wrong, they won&amp;rsquo;t have to start from the beginning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pure Functions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So far, we&amp;rsquo;ve talked only about functions that change the program state, but idempotency is also a natural property of &lt;em&gt;&amp;ldquo;pure&amp;rdquo;&lt;/em&gt; functions. Pure functions always return the same results for a given set of arguments. They don&amp;rsquo;t modify the passed arguments or depend on external components like non-local variables, databases, or network connections that could change outside the function&amp;rsquo;s control.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;is a pure function, but the following isn&amp;rsquo;t because it refers to non-local variables that can be changed by other functions at any time, and yet it&amp;rsquo;s still idempotent because it doesn&amp;rsquo;t change the program state at all.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;Utilizing idempotent functions, both pure and impure, not only makes your code more robust by minimizing unintended side effects but also makes it easier to reason about your code.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Common SQL Mistakes Developers Make</title>
      <link>https://lackofimagination.org/2023/12/common-sql-mistakes-developers-make/</link>
      <pubDate>Tue, 05 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/12/common-sql-mistakes-developers-make/</guid>
      <description>&lt;p&gt;Over the years I&amp;rsquo;ve seen all sorts of SQL mistakes made by developers on my teams, and I&amp;rsquo;ve made quite a few of them myself. I&amp;rsquo;d like to share what I&amp;rsquo;ve learned with you, starting with security first.&lt;/p&gt;
&lt;h1 id=&#34;table-of-contents&#34;&gt;Table of Contents&lt;/h1&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#security&#34;&gt;Security&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#writing-raw-sql-queries&#34;&gt;Writing Raw SQL queries&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#not-using-prepared-statements&#34;&gt;Not Using Prepared Statements&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#data-integrity&#34;&gt;Data Integrity&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#missing-primary-keys&#34;&gt;Missing Primary Keys&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#missing-foreign-keys&#34;&gt;Missing Foreign Keys&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#not-using-transactions&#34;&gt;Not Using Transactions&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#no-order-by&#34;&gt;No ORDER BY&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#performance&#34;&gt;Performance&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#inadequate-indexes&#34;&gt;Inadequate Indexes&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#overusing-select-&#34;&gt;Overusing SELECT *&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#not-using-limit-clauses&#34;&gt;Not Using LIMIT Clauses&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#running-sql-queries-inside-a-loop&#34;&gt;Running SQL Queries Inside a Loop&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#using-where-in-with-a-large-list-of-values&#34;&gt;Using WHERE IN with a Large List of Values&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#running-sql-queries-one-by-one&#34;&gt;Running SQL Queries One by One&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#too-many-updates&#34;&gt;Too many UPDATEs&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#testing-with-small-data-sets&#34;&gt;Testing with Small Data Sets&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#using-orms&#34;&gt;Using ORMs&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#change-management&#34;&gt;Change Management&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#not-using-migrations&#34;&gt;Not Using Migrations&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;

&lt;h4 id=&#34;security&#34;&gt;Security&lt;/h4&gt;
&lt;h5 id=&#34;writing-raw-sql-queries&#34;&gt;Writing Raw SQL queries&lt;/h5&gt;
&lt;p&gt;Even if everyone on your team is careful, sooner or later, someone will inadvertently pass unsanitized user input to a query, inviting various &lt;a href=&#34;https://en.wikipedia.org/wiki/SQL_injection&#34;&gt;SQL injection&lt;/a&gt; attacks. Use an SQL query builder like &lt;a href=&#34;https://knexjs.org&#34;&gt;Knex&lt;/a&gt; instead. SQL query builders allow you to programmatically create queries and automatically sanitize all inputs using &lt;a href=&#34;https://en.wikipedia.org/wiki/Prepared_statement&#34;&gt;prepared statements&lt;/a&gt;, but they do still allow running raw SQL queries for those rare occasions that you need them.&lt;/p&gt;
&lt;h5 id=&#34;not-using-prepared-statements&#34;&gt;Not Using Prepared Statements&lt;/h5&gt;
&lt;p&gt;If you really have to run raw SQL queries, make sure that you use prepared statements. Yes, it&amp;rsquo;s more work, but that&amp;rsquo;s one of the reasons why we are using SQL query builders in the first place.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;data-integrity&#34;&gt;Data Integrity&lt;/h4&gt;
&lt;h5 id=&#34;missing-primary-keys&#34;&gt;Missing Primary Keys&lt;/h5&gt;
&lt;p&gt;Primary keys serve as unique identifiers for each row in a database table. For example, each customer must have a unique, usually numeric, id. Other fields uniquely identifying a customer such as e-mail addresses and phone numbers might change, but a customer&amp;rsquo;s id never changes. If a table doesn&amp;rsquo;t explicitly rely on the primary key for relationships such as many-to-many intermediary tables, you may omit them, but those cases are few and far between. When in doubt, use primary keys.&lt;/p&gt;
&lt;h5 id=&#34;missing-foreign-keys&#34;&gt;Missing Foreign Keys&lt;/h5&gt;
&lt;p&gt;Foreign keys link data in one table to the data in another table. Their primary purpose is to enforce relationships between tables. For example, a foreign key in an &amp;ldquo;Order&amp;rdquo; table may reference the primary key in a &amp;ldquo;Customer&amp;rdquo; table. It&amp;rsquo;s not possible to delete a customer leaving their orders orphaned when such a foreign key is in place. The opposite is true as well. It&amp;rsquo;s not possible to add an order from a non-existent customer.&lt;/p&gt;
&lt;p&gt;When you have a good set of foreign keys in your database, you know that your database has your back when your application attempts to do something it shouldn&amp;rsquo;t.&lt;/p&gt;
&lt;h5 id=&#34;not-using-transactions&#34;&gt;Not Using Transactions&lt;/h5&gt;
&lt;p&gt;Transactions allow you to group multiple operations into a single logical unit of work. They are essential in dealing with complex operations that involve multiple steps. Either all steps in the transaction are completed successfully, or none of them are. For example, when placing an order, you may add an entry to the &amp;ldquo;Order&amp;rdquo; table and update the stock values of the products purchased in the &amp;ldquo;Product&amp;rdquo; table. Without transactions, if an error occurs after the order is created in the database, you end up with incorrect stock numbers.&lt;/p&gt;
&lt;h5 id=&#34;no-order-by&#34;&gt;No ORDER BY&lt;/h5&gt;
&lt;p&gt;This one is relatively innocuous because it doesn&amp;rsquo;t directly affect data integrity, but queries without ORDER BY might return results in unpredictable ways. Developers often make the assumption that query results are always returned in the same order, but this isn&amp;rsquo;t guaranteed unless you explicitly use ORDER BY.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;performance&#34;&gt;Performance&lt;/h4&gt;
&lt;h5 id=&#34;inadequate-indexes&#34;&gt;Inadequate Indexes&lt;/h5&gt;
&lt;p&gt;Having just a primary key in a table isn&amp;rsquo;t enough. As soon as you start to use columns other than the primary key in WHERE conditions, the database won&amp;rsquo;t be able to use indexes, and performance might suffer greatly as a result. Queries that take milliseconds to complete might take seconds or even minutes. If you are having performance issues, the first place to look at is indexes. Adding the right index can significantly improve an application&amp;rsquo;s performance, often by thousands of times.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s tempting to add indexes to every single column in a table, but there are drawbacks to that approach. Databases can&amp;rsquo;t always use multiple indexes at the same time. Also, they need to update every single index after every write operation (INSERT, UPDATE, DELETE). Finally, your data storage requirements can easily double or more, so it pays to be selective when adding indexes to a table.&lt;/p&gt;
&lt;h5 id=&#34;overusing-select-&#34;&gt;Overusing SELECT *&lt;/h5&gt;
&lt;p&gt;Retrieving all columns using &amp;ldquo;SELECT *&amp;rdquo; may be convenient, but it can negatively impact performance. The database needs to retrieve and transfer entire rows, and your application needs to convert all incoming data to an internal format. These operations consume more CPU time, memory, and network bandwidth when there are many unnecessary columns included in the result set.&lt;/p&gt;
&lt;h5 id=&#34;not-using-limit-clauses&#34;&gt;Not Using LIMIT Clauses&lt;/h5&gt;
&lt;p&gt;When you need only a small subset of a result, using LIMIT may greatly improve performance rather than retrieving the whole result and pruning the unnecessary parts in code.&lt;/p&gt;
&lt;h5 id=&#34;running-sql-queries-inside-a-loop&#34;&gt;Running SQL Queries Inside a Loop&lt;/h5&gt;
&lt;p&gt;It may not always be obvious which functions access the database to return a result. Database operations are naturally slow. Accidentally calling SQL queries inside a loop may cause your application to come to a crawl, whereas converting the function to return multiple results, either using WHERE IN or some other method, will likely work much faster.&lt;/p&gt;
&lt;h5 id=&#34;using-where-in-with-a-large-list-of-values&#34;&gt;Using WHERE IN with a Large List of Values&lt;/h5&gt;
&lt;p&gt;While there are good use cases for WHERE IN, it&amp;rsquo;s a good idea to keep its limits in mind. When there are a large list of values, some databases might not use indexes properly, and some also impose a limit on the number of values that can be used.&lt;/p&gt;
&lt;h5 id=&#34;running-sql-queries-one-by-one&#34;&gt;Running SQL Queries One by One&lt;/h5&gt;
&lt;p&gt;SQL queries are slow. Whenever you can, you should run SQL queries in parallel. For example, in Node.js you can use &lt;em&gt;Promise.all&lt;/em&gt; to run multiple SQL queries at the same time.&lt;/p&gt;
&lt;h5 id=&#34;too-many-updates&#34;&gt;Too many UPDATEs&lt;/h5&gt;
&lt;p&gt;Many relational databases don&amp;rsquo;t normally update rows in place &amp;ndash; they create new rows instead and mark the old ones &amp;ldquo;dead&amp;rdquo;. Dead rows are usually reclaimed by the database after some time. If you need to make a great number of updates for things like increasing product view counters, database performance might start to suffer. In cases like this, it&amp;rsquo;s a good idea to augment your relational database with a data store like &lt;a href=&#34;https://redis.io&#34;&gt;Redis&lt;/a&gt; that can handle frequent updates.&lt;/p&gt;
&lt;h5 id=&#34;testing-with-small-data-sets&#34;&gt;Testing with Small Data Sets&lt;/h5&gt;
&lt;p&gt;A query may run just fine when there are 1K rows in a table, but what happens when there are 100K, 1M, or 10M rows? Critical parts of the code must be tested with realistic data sets.&lt;/p&gt;
&lt;h5 id=&#34;using-orms&#34;&gt;Using ORMs&lt;/h5&gt;
&lt;p&gt;Unlike SQL query builders, using Object-Relational Mapping (ORM) frameworks like Hibernate in Java that automatically generate SQL queries for you to populate objects might add an unacceptable overhead for some use cases. A seemingly simple query in an ORM can easily trigger tens of side queries. It&amp;rsquo;s also difficult to optimize or customize the generated queries to achieve better performance.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;change-management&#34;&gt;Change Management&lt;/h4&gt;
&lt;h5 id=&#34;not-using-migrations&#34;&gt;Not Using Migrations&lt;/h5&gt;
&lt;p&gt;Changes are made to the database schema over the lifetime of an application. All schema changes must be stored in immutable migration files so that database schema changes can be made and tracked automatically. Ad-hoc changes without explicit change management might lead to a lot of headaches during deployment.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;That&amp;rsquo;s a wrap. If there are other common SQL mistakes I&amp;rsquo;ve missed, please let me know, and I will update the article.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Practical Defensive Programming</title>
      <link>https://lackofimagination.org/2023/11/practical-defensive-programming/</link>
      <pubDate>Sun, 26 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/11/practical-defensive-programming/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Only the paranoid survive.&amp;rdquo; &amp;ndash; Andy Grove&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No software developer in their right mind would trust user inputs in an application. All user inputs need to be validated and sanitized, otherwise nasty security vulnerabilities like SQL injection and Cross-Site Scripting (XSS) attacks might happen. Once user inputs pass validation and start to go through internal layers of code, many developers assume that there isn&amp;rsquo;t much need to validate inputs anymore, but validation isn&amp;rsquo;t just about security, it&amp;rsquo;s also about correctness.&lt;/p&gt;
&lt;p&gt;Many bugs are caused by passing the wrong type of data to a function such as passing a string when an integer is expected or passing an array with fewer fields than necessary. Language features like static type checking is good at detecting many type errors during compilation, but can&amp;rsquo;t catch everything in runtime like Java&amp;rsquo;s infamous NullPointerException.&lt;/p&gt;
&lt;p&gt;As for TypeScript, which adds type annotations to JavaScript, there are no runtime checks at all after the code is transpiled to JavaScript. Even if you have type annotations for your entire code base, any inputs coming from a database or an external API can still be of a wrong type.&lt;/p&gt;
&lt;p&gt;One of the most effective methods for detecting invalid types is to validate function arguments &lt;em&gt;in runtime&lt;/em&gt;. Validating every single argument can get tedious fast though, with line after line of if statements. Fortunately, there are some good data validation libraries out there like &lt;a href=&#34;https://joi.dev&#34;&gt;Joi&lt;/a&gt; and &lt;a href=&#34;https://zod.dev&#34;&gt;Zod&lt;/a&gt; to make data validation easier, but it can still get long-winded at times, and you wouldn&amp;rsquo;t really want to give developers any excuse to skip data validation :)&lt;/p&gt;
&lt;p&gt;Instead of using an all-purpose validation library designed for validating a wide range of data types, it might make more sense to use a library tailor-made to validate function arguments. With that idea in mind, I&amp;rsquo;ve created a lightweight JavaScript function argument validation library called &lt;a href=&#34;https://github.com/aycangulez/fn-arg-validator&#34;&gt;fn-arg-validator&lt;/a&gt;. Here&amp;rsquo;s a quick example of how it works:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fn-arg-validator&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;birthDate&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;valid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;date&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Thomas&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Anderson&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1971-09-13&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// [WARN] 1971-09-13 failed date check 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ve decided to use a functional style interface where you pass type check functions (built-in or your own) for each function argument, and as the final argument, you simply pass the &lt;em&gt;arguments&lt;/em&gt; object to avoid typing the function parameters again.&lt;/p&gt;
&lt;p&gt;When validating arrow function arguments, the &lt;em&gt;arguments&lt;/em&gt; object isn&amp;rsquo;t available, so you need to provide the parameter names in an array like &lt;em&gt;[firstName, lastName, birthDate]&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The example code above will normally log a warning and continue to run, but you may choose to check the return value of &lt;strong&gt;is.valid&lt;/strong&gt; to stop the execution of the rest of the code. The decision depends on how you want to handle type errors. Sometimes, a warning is enough, and sometimes you need to be strict and throw an error.&lt;/p&gt;
&lt;h4 id=&#34;boundary-checks-and-maybe-types&#34;&gt;Boundary Checks and Maybe Types&lt;/h4&gt;
&lt;p&gt;In addition to strict type checks, it&amp;rsquo;s possible to do things like string length checks, and use maybe types that also accept undefined/null values:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;birthDate&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;valid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringBetween&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stringLTE&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;maybeDate&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;object-property-and-type-checks&#34;&gt;Object Property and Type Checks&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;is.objectWithProps&lt;/strong&gt; can be used to check if an object has the specified properties and those properties have the correct types.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userObjectProps&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;number&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;birthDate&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;date&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;updateUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;valid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;objectWithProps&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userObjectProps&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Invalid user object&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;updateUser&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Thomas&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Anderson&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;birthDate&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1971-09-13&amp;#39;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;[WARN] {&amp;#34;id&amp;#34;:1,&amp;#34;firstName&amp;#34;:&amp;#34;Thomas&amp;#34;,&amp;#34;lastName&amp;#34;:&amp;#34;Anderson&amp;#34;,&amp;#34;birthDate&amp;#34;:&amp;#34;1971-09-13&amp;#34;} failed objectWithProps check
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;Error: Invalid user object
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can have one-level of nested object property checks as shown below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;valid&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;objectWithProps&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;number&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;objectWithProps&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;d&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;number&lt;/span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;passing-your-own-type-check-functions&#34;&gt;Passing your Own Type Check Functions&lt;/h4&gt;
&lt;p&gt;The beauty of a functional style interface is that you aren&amp;rsquo;t limited to the built-in validation functions, you can simply pass your own. The only requirement is to give your functions names since &lt;strong&gt;is.valid&lt;/strong&gt; uses function names for logging purposes.&lt;/p&gt;
&lt;h4 id=&#34;log-configuration&#34;&gt;Log Configuration&lt;/h4&gt;
&lt;p&gt;By default, fn-arg-validator uses the &lt;em&gt;console&lt;/em&gt; object for logging. However, this can be configured by assigning a different logger to &lt;strong&gt;is.config.log&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The log level can be set by changing the value of &lt;strong&gt;is.config.logLevel&lt;/strong&gt;. The default log level is &lt;em&gt;&amp;lsquo;WARN&amp;rsquo;&lt;/em&gt;, which only logs failed checks. If you would like to see successful validations, you need to set the log level to &lt;em&gt;&amp;lsquo;DEBUG&amp;rsquo;&lt;/em&gt; or higher. To disable all logging, set the log level to &lt;em&gt;&amp;lsquo;OFF&amp;rsquo;&lt;/em&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Finally, to see the complete list of built-in functions, you can take a look at &lt;a href=&#34;https://github.com/aycangulez/fn-arg-validator&#34;&gt;fn-arg-validator&amp;rsquo;s GitHub page&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Making a KEF LS50 Clone</title>
      <link>https://lackofimagination.org/2023/11/making-a-kef-ls50-clone/</link>
      <pubDate>Fri, 17 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/11/making-a-kef-ls50-clone/</guid>
      <description>&lt;p&gt;KEF makes some of the best coaxial speakers in the market, and they use the same driver platform in all their speakers ranging from the entry-level Q series to all the way up to Blade. The drivers in their higher end speakers usually have stronger motors and better crossovers, but the higher prices don&amp;rsquo;t always justify the level of performance improvements.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_q150_and_ls50_meta_side_by_side.jpg&#34;  alt=&#34;KEF Q150 and LS50 Meta side by side.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;KEF Q150 and LS50 Meta side by side.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;KEF&amp;rsquo;s acclaimed LS50 (and later LS50 Meta) speakers use a single coaxial driver which looks identical to the one used in the entry level Q150 from the outside, but the speakers sound quite different. If you equalize both speakers to have about the same frequency response to level the playing field, the LS50 will still have a better defined, less diffuse sound.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_meta_inside.jpg&#34;  alt=&#34;Inside KEF LS50 Meta&amp;#39;s cabinet. Image © KEF&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Inside KEF LS50 Meta&amp;#39;s cabinet. Image © KEF&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The LS50 has a superior cabinet that features thicker walls, substantial cross braces, a flexible port, and finally KEF&amp;rsquo;s version of Constrained Layer Damping (CLD) to minimize internal resonances. Since you can buy two to three pairs of the Q150 for the price of one pair of LS50 Meta depending on whether there&amp;rsquo;s a sale or not, I decided to build an LS50 clone using a Q150 driver to see if they would sound similar after equalization. The clone wouldn&amp;rsquo;t have the beautiful curved front baffle and better crossover of LS50 Meta, but it would otherwise be very similar in construction.&lt;/p&gt;
&lt;p&gt;The KEF Q150 is a narrow speaker &amp;ndash; only 18 cm (7&amp;quot;) wide while the LS50 is slightly wider at 20 cm. My version would be even wider at 22 cm as I decided to reuse the original port, which had such a wide flare that it wouldn&amp;rsquo;t fit if the cabinet was narrower.&lt;/p&gt;
&lt;p&gt;The KEF Q150&amp;rsquo;s front baffle is quite thick at 36 mm (1.4&amp;quot;), but the remaining walls are rather thin at 12.7 mm (1/2&amp;quot;). There&amp;rsquo;s only one small horizontal brace and very little stuffing inside, so the cabinet is naturally prone to resonances.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_q150_bare_bones_cabinet.jpg&#34;  alt=&#34;KEF Q150 has a bare-bones cabinet with one small horizontal brace.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;KEF Q150 has a bare-bones cabinet with one small horizontal brace.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;I decided to use 18 mm thick MDF just like the LS50 for all cabinet walls except for the front baffle which would stay the same at 36 mm. The cross braces would also be made out of 18 mm MDF. KEF uses something similar to 3M VHB tape to have a flexible connection between the braces and the cabinet walls as a form of CLD, so I decided to give it a try.&lt;/p&gt;
&lt;p&gt;I started with cutting the interlocking braces and attached them to the surrounding walls by using double sided 3M VHB tape instead of wood glue. The VHB tape is supposed to act as a CLD layer to loosely couple the braces to the cabinet walls.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_braces.jpg&#34;  alt=&#34;Braces support all six walls and the driver.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Braces support all six walls and the driver.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The outside walls are glued to each other with wood glue.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_side_walls.jpg&#34;  alt=&#34;The cabinet is coming together.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The cabinet is coming together.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The front baffle was made out of multiple layers of MDF. I opted to use M4 threaded inserts and screws in place of the original&amp;rsquo;s wood screws, as all it takes a few driver test fits to strip wood screw holes. I also rounded over all cabinet edges except the rear side with a 12.7 mm (1/2&amp;quot;) radius router bit, and sanded everything smooth. The cabinet weighs around 6 kg (~13 pounds).&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_front_baffle_attached.jpg&#34;  alt=&#34;Front baffle was finished and cabinet edges were rounded over.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Front baffle was finished and cabinet edges were rounded over.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The cabinet was painted white with a spray gun with the paint I already had at hand, but I may use a different color scheme in the future.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_painted.jpg&#34;  alt=&#34;The cabinet painted white.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The cabinet painted white.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The KEF Q150 uses a simple first-order crossover board to divide the audio signal between the mid-bass driver and tweeter. Although the crossover board had clearly been designed to be mounted on the back side of the terminal cups, KEF chose not to do that for some reason. They mounted the board on the bottom side of the KEF Q150 cabinet instead. Since the original terminal cups didn&amp;rsquo;t have the necessary standoffs to mount the crossover board, I purchased another set with standoffs, and used M3 nylon screws to mount the board after tapping the standoffs of course.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_q150_driver_and_crossover.jpg&#34;  alt=&#34;The KEF Q150 crossover is mounted to the terminal cups in my version.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The KEF Q150 crossover is mounted to the terminal cups in my version.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Here is the finished cabinet with the driver and trim ring mounted. I reused the rubber grommets from the original to mount the trim ring. I was excited to test the speaker out, so there is no clear coat to protect the paint on the speaker yet.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_finished_front.jpg&#34;  alt=&#34;In my version, the crossover board is mounted on the backside of the terminal cups.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;In my version, the crossover board is mounted on the backside of the terminal cups.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;The center of the port is positioned one-fourth of the way from the right and the top to cancel some of the internal standing waves. The terminal cups are also mounted vertically. I think they look better that way.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_finished_rear.jpg&#34;  alt=&#34;Finished cabinet - rear.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Finished cabinet - rear.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;So, how does it sound? Quite good actually. The original had a great sound stage thanks to the well designed coaxial driver, but was undermined by the cabinet. With the new cabinet, I&amp;rsquo;m getting better bass definition, and the upper notes have better clarity.&lt;/p&gt;
&lt;p&gt;After listening to the speaker, I made impedance measurements of the original KEF Q150 and my LS50 clone. My measurement setup isn&amp;rsquo;t super accurate, but the wiggles that indicate the resonances in the original (orange) are mostly gone in my LS50 clone (green). The difference is especially visible at around 700 Hz.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_and_q150_impedance_measurements.png&#34;  alt=&#34;Impedance measurement (Orange: KEF Q150, Green: LS50 Clone)&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Impedance measurement (Orange: KEF Q150, Green: LS50 Clone)&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Update (August 2, 2025):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Plans for the horizontal and vertical braces are available below (units are in mm):&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_horizontal_brace_plan.png&#34;  alt=&#34;Plan for horizontal brace.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Plan for horizontal brace.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;


 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/kef_ls50_clone_cabinet/kef_ls50_clone_vertical_brace_plan.png&#34;  alt=&#34;Plan for vertical brace.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Plan for vertical brace.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Worth a read:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://assets.kef.com/pdf_doc/LS50Meta/KEF-LS50Meta-LS50WirelessII-WhitePaper.pdf&#34;&gt;KEF LS50 Meta / Wireless II White Paper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Design Patterns vs. First Class Functions</title>
      <link>https://lackofimagination.org/2023/11/design-patterns-vs.-first-class-functions/</link>
      <pubDate>Wed, 15 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/11/design-patterns-vs.-first-class-functions/</guid>
      <description>&lt;p&gt;&lt;em&gt;Design Patterns: Elements of Reusable Object-Oriented Software&lt;/em&gt; is an influential book that describes around two dozen software programming patterns. Over the years much has been written about those patterns to make the confusing parts easier for generations of developers &amp;ndash; additional books got published and endless discussions on things like the difference between &lt;em&gt;&amp;ldquo;Abstract Factory&amp;rdquo;&lt;/em&gt; and &lt;em&gt;&amp;ldquo;Factory Method&amp;rdquo;&lt;/em&gt; took place in various online forums. But really, they shouldn&amp;rsquo;t have been this complicated &amp;ndash; the purpose of the most design patterns can be summarized in just 3 words:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Hide implementation details.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Or more specifically in 7 words:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Hide implementation details behind a common interface.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Granted, the authors of the Design Patterns book famously stated: &lt;em&gt;&amp;ldquo;Program to an interface, not an implementation.&amp;rdquo;&lt;/em&gt;, which means about the same thing, but then they went on to explain in great length how to achieve that goal within the bounds of traditional object oriented languages employing classes, and confused a lot of people along the way.&lt;/p&gt;
&lt;p&gt;When Design Patterns first got published, the year was 1994, a rather primitive time when functional programming languages that support first class functions were rarely used outside academia. These days, though, we have mainstream languages such as JavaScript and Python that can be used to trivially implement many of the design patterns described in the book with only a few lines of code.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s go over five popular design patterns and see how easy it is to implement them in JavaScript, the lingua franca of the web:&lt;/p&gt;
&lt;h4 id=&#34;factory&#34;&gt;Factory&lt;/h4&gt;
&lt;p&gt;Despite the lofty name, a factory function just returns an object. We do that all the time in JavaScript without giving it a second thought.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; {&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;newUser&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Asuka&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Langley&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;newUser&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getFullName&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Prints Asuka Langley
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;decorator&#34;&gt;Decorator&lt;/h4&gt;
&lt;p&gt;Another pattern with a fancy name, the decorator pattern is quite simple: modify an object while keeping its original interface the same. In JavaScript, we modify objects all the time, so I suppose we are all decorators.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// The user service object has been &amp;#34;decorated&amp;#34; with a shiny name property
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;userService&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// You can also create a new &amp;#34;decorated&amp;#34; object (use _.cloneDeep for nested objects)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;anotherUserService&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {...&lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;anotherUserService&amp;#39;&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;command&#34;&gt;Command&lt;/h4&gt;
&lt;p&gt;The Command pattern can actually be quite useful when there is a need to transparently change the behavior of certain functions. Instead of calling a function directly, you call a command function, which in turn calls the original function for you. When there is an intermediate layer between the caller and callee, there are a myriad of possibilities &amp;ndash; you can&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;queue function calls instead of calling them right away,&lt;/li&gt;
&lt;li&gt;log function calls complete with arguments passed,&lt;/li&gt;
&lt;li&gt;cache function results,&lt;/li&gt;
&lt;li&gt;route calls to other functions, great for creating &lt;a href=&#34;https://lackofimagination.org/2023/10/writing-testable-code-a-simpler-way/&#34;&gt;test doubles&lt;/a&gt; among other things.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is a generic command runner object that logs function calls before calling the actual functions. Just make sure to add a name property to the objects you pass to the runner, if it cannot automatically determine the objects&amp;rsquo; names.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	    &lt;span style=&#34;color:#75715e&#34;&gt;// To preserve &amp;#39;this&amp;#39;, we need the parent object
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parentObj&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	    &lt;span style=&#34;color:#75715e&#34;&gt;// If the second argument is empty, the parent object is called as is
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parentObj&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]] &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parentObj&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	    &lt;span style=&#34;color:#75715e&#34;&gt;// Get arguments to pass to the function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;argList&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [].&lt;span style=&#34;color:#a6e22e&#34;&gt;slice&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;call&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;arguments&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	     &lt;span style=&#34;color:#75715e&#34;&gt;// Log function calls
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	    &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;([&lt;span style=&#34;color:#a6e22e&#34;&gt;parentObj&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;func&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, ...&lt;span style=&#34;color:#a6e22e&#34;&gt;argList&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	    &lt;span style=&#34;color:#75715e&#34;&gt;// Call function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;func&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;apply&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;parentObj&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;argList&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user01&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;create&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Shinji&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Ikari&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user02&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;create&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Asuka&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Langley&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* Prints:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;    [ &amp;#39;userService&amp;#39;, &amp;#39;create&amp;#39;, &amp;#39;Shinji&amp;#39;, &amp;#39;Ikari&amp;#39; ],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;    [ &amp;#39;userService&amp;#39;, &amp;#39;create&amp;#39;, &amp;#39;Asuka&amp;#39;, &amp;#39;Langley&amp;#39; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;strategy&#34;&gt;Strategy&lt;/h4&gt;
&lt;p&gt;In JavaScript, it&amp;rsquo;s common to pass a function as an argument to customize the behavior of another function. Here&amp;rsquo;s an example from &lt;a href=&#34;https://lodash.com&#34;&gt;Lodash&lt;/a&gt;, a popular utility library:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lodash&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;collection&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;pickBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;collection&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isNumber&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Prints { &amp;#39;a&amp;#39;: 1, &amp;#39;c&amp;#39;: 3 }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;pickBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;collection&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isString&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Prints { b: &amp;#39;2&amp;#39; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;chain-of-responsibility&#34;&gt;Chain of Responsibility&lt;/h4&gt;
&lt;p&gt;If you&amp;rsquo;ve used a popular web service framework like &lt;a href=&#34;https://expressjs.com&#34;&gt;Express&lt;/a&gt; or &lt;a href=&#34;http://restify.com&#34;&gt;Restify&lt;/a&gt;, you&amp;rsquo;ve used chain of responsibility. Each web service request needs to go through several handler functions (aka middleware) to handle things like logging, rate limiting, authentication, and authorization before processing the actual request. Any handler can break the chain and throw an error. The implementation is left as an exercise to the reader, but the process is quite simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Register handler functions.&lt;/li&gt;
&lt;li&gt;Call them in order.&lt;/li&gt;
&lt;li&gt;Check each handler function&amp;rsquo;s return value or the exception it has thrown to decide whether to call the next handler or exit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example of the pattern in action from Restify:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;restify&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;createServer&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Register global handler functions that are called for all routes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;use&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;webRequest&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;use&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;restify&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;plugins&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;throttle&lt;/span&gt;({&lt;span style=&#34;color:#a6e22e&#34;&gt;burst&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;rate&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ip&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;}));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;use&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;authentication&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;verify&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Add custom handlers for each route below that are run after the global handlers
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Run an authorization handler and if all is well, return user profile data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/profile/:userId&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;authorization&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;canViewProfile&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;userController&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getProfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Only admins can update admin settings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/admin/settings&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;authorization&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isAdmin&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;adminController&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;updateSettings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;David Wheeler famously said: &amp;ldquo;We can solve any problem by introducing an extra level of indirection.&amp;rdquo; Many a classic design pattern is nothing but an extra layer of abstraction. Classes aren&amp;rsquo;t usually the best way to implement those abstractions, but first class functions that can be passed around as arguments are.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Debugging is Programming</title>
      <link>https://lackofimagination.org/2023/11/debugging-is-programming/</link>
      <pubDate>Tue, 07 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/11/debugging-is-programming/</guid>
      <description>&lt;p&gt;First things first&amp;hellip;&lt;/p&gt;
&lt;h4 id=&#34;how-to-write-production-quality-code-in-12-steps&#34;&gt;How to write production quality code in 12 steps:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Understand the problem.&lt;/li&gt;
&lt;li&gt;Divide the problem into smaller chunks if possible.&lt;/li&gt;
&lt;li&gt;Choose the data structures and algorithms to use for the solution.&lt;/li&gt;
&lt;li&gt;Write the code.&lt;/li&gt;
&lt;li&gt;Run the code. Be surprised if it runs on the first try. Otherwise, fix any errors the compiler has thrown at you.&lt;/li&gt;
&lt;li&gt;Test the program behavior. If it functions correctly, go to step 9.&lt;/li&gt;
&lt;li&gt;Guess which part of your code is responsible for the incorrect behavior. If necessary, add breakpoints or print statements to your code to pinpoint the bug.&lt;/li&gt;
&lt;li&gt;Fix the bug. Go to step 5.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re almost there. Commit your code and open a merge request for code review.&lt;/li&gt;
&lt;li&gt;Assuming there&amp;rsquo;s a Continuous Integration (CI) process in place, see if some of the automated tests failed. If so, go to step 7.&lt;/li&gt;
&lt;li&gt;Your code has been deployed to the test environment. The product manager and the QA team test your code. If they discover any bugs you have missed, go to step 7.&lt;/li&gt;
&lt;li&gt;Your code is merged into the main branch and deployed to production. A user finds a bug that you, the product manager, and the QA team have missed. Go to step 7.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See any patterns above?&lt;/p&gt;
&lt;p&gt;No?&lt;/p&gt;
&lt;p&gt;Hint: There are quite a few references to step 7.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ve probably seen programmers calmly writing code at 200 keystrokes per minute to solve hideously complex problems under strict deadlines, and the programs they wrote run perfectly on the first try, often only a few seconds before something blows up real good. Wait, what? I&amp;rsquo;m talking about Hollywood movies of course in which programmers can routinely achieve amazing feats while in the real world, we mere mortals have to do something called debugging, and do lots of it.&lt;/p&gt;
&lt;p&gt;Debugging is often described as something separate from writing code as I did in the 12-step coding process above, but that&amp;rsquo;s not quite true. Unless you&amp;rsquo;re writing very simple programs, writing code involves a lot of trial and error, and it&amp;rsquo;s not uncommon to write, run, and revise a program hundreds of times before submitting it for review. In other words, debugging is done from day one.&lt;/p&gt;
&lt;p&gt;Some software developers like to debug code, some don&amp;rsquo;t. As for me, I&amp;rsquo;m firmly in the like camp. I think of debugging as doing detective work, and unlike detectives who can successfully solve only some of their cases, our success rate is much higher, approaching close to 100% because we have one magic trick up our sleeves &amp;ndash; the ability to reproduce bugs. If you can consistently reproduce the conditions that trigger a bug, it&amp;rsquo;s inevitable to find the reason because code is deterministic. It will behave the same every time. Granted, it may not be exactly deterministic when multiple branches of code are run in parallel, but there are ways to write well behaved parallel code.&lt;/p&gt;
&lt;p&gt;Some software developers tend to fault compilers, databases, and other external components for errors in their programs, but almost all the time there is a fault in the programs they&amp;rsquo;ve written. If you have the steps to reproduce a bug, you&amp;rsquo;re half way finding what causes a bug. Just running the relevant sections of your code step by step and seeing how it behaves should be enough in most cases. Remember, determinism is your friend.&lt;/p&gt;
&lt;h4 id=&#34;what-causes-bugs&#34;&gt;What causes bugs?&lt;/h4&gt;
&lt;p&gt;From most common to less common:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Syntax errors:&lt;/strong&gt; They are the most obvious because your code won&amp;rsquo;t run at all, and there is a compiler error message telling why. In dynamic languages like JavaScript, your code may still run at first, but it will fail with a compiler error message when the code block containing the syntax error is executed. Today&amp;rsquo;s Integrated Development Environments (IDEs) can usually show syntax errors as you are typing code, so they&amp;rsquo;re easy to spot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type errors:&lt;/strong&gt; Passing a variable with a different type than expected such as passing a string value to a variable expecting an integer may cause issues depending on the programming language used and whether the receiving code can handle different types. Passing undefined values is also a problem that may cause errors such as the infamous &lt;em&gt;NullPointerException&lt;/em&gt; error in Java, and the &lt;em&gt;&amp;ldquo;Cannot read property &amp;lsquo;x&amp;rsquo; of undefined&amp;rdquo;&lt;/em&gt; errors in JavaScript.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logic errors:&lt;/strong&gt; In most programs, the most important part is the &amp;ldquo;business logic&amp;rdquo; as implemented in code. Either the logic is implemented incorrectly or it is implemented correctly, but some edge cases are overlooked. Extensive unit and integration testing is the key to identify logic errors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State errors:&lt;/strong&gt; When a program starts to behave erratically, if restarting it solves the problem, the cause is likely to be the corrupt internal state of the program. Over time, the internal shared state may change unexpectedly when different parts of the program updates it. Using immutable data structures whenever it&amp;rsquo;s practical helps keep internal state consistent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency errors:&lt;/strong&gt; One common error is race conditions &amp;ndash; function A may depend on the result returned by function B running in parallel, and if the function B runs late function A fails. Developers who are coming from traditional programming languages may experience quite a surprise when they write their first programs in JavaScript/Node.js, which doesn&amp;rsquo;t normally wait for asynchronous statements to finish and go on executing the next line. As for programming languages that rely on threads for concurrency, writing thread-safe code is critical.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment errors:&lt;/strong&gt; These errors are caused by the environment a program is running in such as out of memory/disk space errors and network time outs. Some of these errors can be solved by simply increasing server resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;debugging-methods&#34;&gt;Debugging Methods&lt;/h4&gt;
&lt;p&gt;There are many ways to debug a program, but the following are the most common:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Print statements:&lt;/strong&gt; Temporarily sprinkling a few print statements here and there to display variable values and track control flow is one of the oldest methods of debugging, but it&amp;rsquo;s still effective especially when debugging concurrent code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Breakpoints:&lt;/strong&gt; Pausing program execution when it reaches a certain point, and then inspecting variables as you step through the code line by line can be quite useful to understand code behavior. For debugging concurrent code though, use print statements since you can&amp;rsquo;t really step through concurrent code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging:&lt;/strong&gt; When you make your print statements more descriptive, categorize them by levels of severity such as INFO and ERR, and store them permanently in an external log file or database, what you get is logging &amp;ndash; an indispensable tool to trace code execution flow. Logging comes handy for other purposes such as user analytics, but that&amp;rsquo;s outside the scope of this article.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version Control:&lt;/strong&gt; With version control systems like Git, you can easily revert to a past version of your code if one of the recent changes you had made introduced a bug.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profiling:&lt;/strong&gt; If you are having performance problems, you can use profiling tools to discover in which functions your program consumes CPU time the most.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;It&amp;rsquo;s hard to get good at programming without getting good at debugging since debugging is such an essential part of the process. Debugging the code others have written is also a great way to learn how an unfamiliar code base works. Granted, debugging isn&amp;rsquo;t always pleasant, but the better you get at debugging, the faster you can write code &amp;ndash; maybe not as fast as our Hollywood programmers, but we can only hope!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Writing Testable Code: A Simpler Way</title>
      <link>https://lackofimagination.org/2023/10/writing-testable-code-a-simpler-way/</link>
      <pubDate>Mon, 30 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/10/writing-testable-code-a-simpler-way/</guid>
      <description>&lt;p&gt;There are many ways to write automated tests, and you may have heard about test doubles as a way to make testing easier. Some parts of our code can be hard to test, so we replace those parts with fake test doubles. The main reason for this is that external components, such as databases and web services, cannot &lt;em&gt;easily&lt;/em&gt; be restored to a consistent state for each test run.&lt;/p&gt;
&lt;p&gt;For example, if you&amp;rsquo;re testing whether a new user account is created successfully, you&amp;rsquo;ll need to provide a randomly generated email address or phone number during each test. Otherwise, the test would fail. There are workarounds, of course &amp;ndash;you could delete the user account after the test&amp;ndash; but that adds an extra step to an already slow process. This brings me to the second reason why test doubles are preferred: external components can be much slower than a test double simply returning a success value.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Now wait,&amp;rdquo; you might ask, &amp;ldquo;aren&amp;rsquo;t we supposed to test whether a new user has actually been created in the database? Isn&amp;rsquo;t that the whole point?&amp;rdquo; Well, not really. The actual account creation process is probably the least interesting part to test because the database will almost always do its job and create the corresponding user record. What we really want to test is under what conditions the account creation would fail &amp;ndash; for example, when a duplicate email address is used or when some fields are missing or contain invalid data. These control conditions, which fall under &amp;ldquo;business logic,&amp;rdquo; are typically the most important to test.&lt;/p&gt;
&lt;p&gt;It appears that there are about half a dozen types of test doubles with interesting names like fakes, spies, and mocks. There are also lengthy articles explaining the differences between them. Apparently, there&amp;rsquo;s a need for all those variations, but for me a test double is a test double. In an expressive language like JavaScript, with its support for first-class functions and closures, you can likely go a long way by simply tracking which functions are called with which arguments to determine whether your code dealing with external components is working as it should.&lt;/p&gt;
&lt;p&gt;So, I came up with a tiny JavaScript utility called &lt;a href=&#34;https://github.com/aycangulez/fn-tester&#34;&gt;fn-tester&lt;/a&gt; to record function calls and define test doubles. I deliberately kept it simple &amp;ndash; the first version is only 14 lines long (except for tests of course), and I hope it stays that way.&lt;/p&gt;
&lt;p&gt;fn-tester has only three options and one method. It requires running functions indirectly, using a variation of the Command design pattern. This approach is necessary to intercept all function calls and preserve the value of &amp;ldquo;this&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fn-tester&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Instead of parentObj.methodName(argument1, argument2, ...), use:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;parentObj&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;methodName&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;argument1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;argument2&lt;/span&gt;, ...);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// For simple functions, use:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;myFunction&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;argument1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;argument2&lt;/span&gt;, ...);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, you might argue that changing all function calls like that isn&amp;rsquo;t simple at all. However, there&amp;rsquo;s no need to modify every single function call. In a typical Model-View-Controller (MVC) application, it&amp;rsquo;s usually enough to focus on controller-level functions that manage interactions between multiple services. Services that handle databases and remote APIs will need test doubles in any case.&lt;/p&gt;
&lt;p&gt;fn-tester can be configured by simply changing the &lt;strong&gt;test&lt;/strong&gt; property, and the defaults are:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;enabled&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;calls&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [], &lt;span style=&#34;color:#a6e22e&#34;&gt;doubles&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [] };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When &lt;strong&gt;enabled&lt;/strong&gt; is true, fn-tester starts to record function calls in &lt;strong&gt;calls&lt;/strong&gt;. It also runs the test double functions stored in &lt;strong&gt;doubles&lt;/strong&gt; (if any) instead of the originals.&lt;/p&gt;
&lt;p&gt;Here is how to define test doubles for a simple user account creation controller:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// (...)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bcrypt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;hash&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;where&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;head&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;insertUser&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;insert&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; }).&lt;span style=&#34;color:#a6e22e&#34;&gt;returning&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;getUserByEmail&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;existingUser&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;existingUser&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;reject&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;error.duplicateEmail&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hashPassword&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;passwordHash&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;userService&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;insertUser&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;passwordHash&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;enabled&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;calls&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;doubles&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hash&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;insertUser&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;some@email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pass&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;(() =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;calls&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When fn-tester finds a function in &lt;strong&gt;test.doubles&lt;/strong&gt; with the same name as the function called, it runs the test double instead of the original. &lt;strong&gt;test.calls&lt;/strong&gt; contains the function calls complete with the arguments passed.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;createUser&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;some@email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pass&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;getUserByEmail&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;some@email&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hashPassword&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pass&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;insertUser&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;some@email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hash&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;test.calls&lt;/strong&gt; is useful to determine if a function is called and has received the correct arguments.&lt;/p&gt;
&lt;p&gt;Finally, here is an example of how fn-tester can be used with Chai and Mocha, a popular assertion library and testing framework respectively:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fn-tester&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chai&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;chai&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chaiAsPromised&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;chai-as-promised&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;should&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;chai&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;should&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;chai&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;use&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;chaiAsPromised&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;should&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// (...)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;describe&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;createUser&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;it&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;should go through with account creation&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;enabled&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;calls&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;doubles&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getUserByEmail&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashPassword&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hash&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;insertUser&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Promise.&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;createUser&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;some@email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pass&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;calls&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;should&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;be&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .&lt;span style=&#34;color:#a6e22e&#34;&gt;an&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;array&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .&lt;span style=&#34;color:#a6e22e&#34;&gt;that&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;deep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;([&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;getUserByEmail&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;some@email&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;that&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;deep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;([&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hashPassword&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pass&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;that&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;deep&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;includes&lt;/span&gt;([&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;insertUser&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;some@email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hash&amp;#39;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Three Aspects of B2B Software Development</title>
      <link>https://lackofimagination.org/2023/10/three-aspects-of-b2b-software-development/</link>
      <pubDate>Mon, 23 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/10/three-aspects-of-b2b-software-development/</guid>
      <description>&lt;p&gt;B2B (Business-to-Business) software is different from B2C (Business-to-Consumer) software for several reasons, but the most important one is this: In B2B, each customer isn&amp;rsquo;t an individual but a company with its own unique needs and workflows. If your software isn&amp;rsquo;t compatible with the way a company operates and there are no convenient workarounds, you risk losing their business, which brings me to the first important aspect of B2B software&amp;hellip;&lt;/p&gt;
&lt;h4 id=&#34;customizations&#34;&gt;Customizations&lt;/h4&gt;
&lt;p&gt;In the B2B software world, your software may solve every problem a company has, but it may still end up coming short because it doesn&amp;rsquo;t solve those problems &lt;em&gt;just&lt;/em&gt; the way they need. As a result, both prospective and current customers often request changes to the software. The most common requests we encounter as a maker of &lt;a href=&#34;https://faradai.ai&#34;&gt;carbon accounting and energy management platforms&lt;/a&gt; include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom dashboards&lt;/li&gt;
&lt;li&gt;Custom reports&lt;/li&gt;
&lt;li&gt;Custom fields or options when entering data&lt;/li&gt;
&lt;li&gt;Custom notifications&lt;/li&gt;
&lt;li&gt;Custom logos and wording&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having a built-in ability to create modular, preferably drag and drop dashboards is an important asset in meeting custom dashboard requests. Custom reports are equally important, and it&amp;rsquo;s often possible to arrive at a common reporting format, which may benefit more than one customer.&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t designed your B2B software with customization in mind, you will likely have a lot of problems down the line with your software looking like the car designed by Homer Simpson with seemingly unrelated features sticking out like a sore thumb.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/tapped_out_the_homer.png&#34;  alt=&#34;Custom car design by none other than Homer Simpson. Image by Electronic Arts&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Custom car design by none other than Homer Simpson. Image by Electronic Arts&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;In order to be a product company, not a consultancy, you should avoid one-off custom features as much as possible and focus on features that can be customized instead. It&amp;rsquo;s a tight rope to walk on &amp;ndash; features that support customization introduce complexity, and that may not only make your software harder to use for the end users, but also make it harder to maintain for you. One-off features, on the other hand, can easily turn out to be a maintenance nightmare because they are specific to one customer only, are not usually tested as well as the core software features, and are easier to break after software upgrades.&lt;/p&gt;
&lt;h4 id=&#34;integrations&#34;&gt;Integrations&lt;/h4&gt;
&lt;p&gt;After customizations, integrating with company systems comes up as a common customer requirement, and the most requested integration is probably SSO (Single Sign-On). If a large number of employees are going to use your software, Active Directory integration (usually via LDAP) is the most popular SSO request. It&amp;rsquo;s a relatively easy integration to do, isn&amp;rsquo;t unique to a single customer, and saves you from dealing with support requests about user account issues like forgotten passwords.&lt;/p&gt;
&lt;p&gt;There are basically two types of integrations from a technical point of view &amp;ndash; integrations using your software&amp;rsquo;s API (Application Programming Interface) and integrations using everything else. The former is the ideal one because you have the most control and usually the least amount of additional work to do other than documenting your API well, but it requires the company to have their own software developers or contractors to do the integration.&lt;/p&gt;
&lt;p&gt;Other integrations may require connecting to company systems by using APIs (SOAP used to be common, but REST APIs using JSON is getting popular for data exchange) and reading and parsing files (usually CSV) on an FTP site, which in this day and age is still popular. You may also push data to other software products such as internal company dashboards. Integrations that depend on internal company systems tend to be more fragile due to the increased number of dependencies and may occasionally break for reasons as simple as the company IT department&amp;rsquo;s decision to change the address of an internal API service without telling you.&lt;/p&gt;
&lt;p&gt;These days B2B cloud software is quite common, but some customers may still request on-premise installations of your software due to regulations or their 3rd party software policies. While on-premise installations are not really integrations per se, they still require you to adhere to the internal software deployment and testing procedures of a company, and can turn out to be gargantuan tasks in some cases, so beware.&lt;/p&gt;
&lt;h4 id=&#34;backward-compatibility&#34;&gt;Backward Compatibility&lt;/h4&gt;
&lt;p&gt;Unlike many B2C software where &amp;ldquo;fickle&amp;rdquo; users can switch from one software to another on a whim, switching from B2B software can be quite challenging especially when there are significant switching and training costs are involved. When your software is customized to the needs of a company, has several integrations with that company&amp;rsquo;s proprietary systems, and after who knows how many meetings that spanned months or even years to arrive at this successful outcome, there has to be a &lt;em&gt;really&lt;/em&gt; good reason for a company&amp;rsquo;s decision makers to switch to another software provider and go through the whole process all over again.&lt;/p&gt;
&lt;p&gt;With B2C software you are allowed to move fast and break things especially in the beginning. In B2B software, however, backward compatibility is important. Your software doesn&amp;rsquo;t sit in isolation &amp;ndash; it&amp;rsquo;s connected to other systems, and a lot of people depend on your software to do their jobs. Just like it&amp;rsquo;s hard for a company to switch to another provider, it&amp;rsquo;s also hard to make major changes to existing B2B software with a large user base. And there lies the dilemma &amp;ndash; how to change or improve functionality without making potentially breaking changes and requiring extensive training. At the end of the day, if some things need to change in your software, it all depends on whether the new features/improvements are worth the trouble for the customer. At large companies it&amp;rsquo;s not uncommon to come across software with user interfaces that look like they were designed 20 years ago, and you can probably guess why.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In my opinion, over time the relationship of B2B software providers with their customers starts to resemble a marriage for lack of a better word, especially the software in question is vital to the customers&amp;rsquo; operations. A well designed B2B software attuned to each customer&amp;rsquo;s specific needs may not win any user interface design awards, but it gets the job done, and that&amp;rsquo;s what really counts in the end.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Small Software Development Team Structure</title>
      <link>https://lackofimagination.org/2023/10/small-software-development-team-structure/</link>
      <pubDate>Sun, 15 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/10/small-software-development-team-structure/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve worked at software development startups including my own throughout my career. I&amp;rsquo;ve worked as a software developer, tech lead, and CTO. I&amp;rsquo;ve written code, managed servers, created user interfaces, designed web sites, prepared documentation, and even done some marketing. I&amp;rsquo;ve hired and assisted in hiring dozens of people: both developers and non-developers. But, first things first, if you are a cash-strapped startup, you can usually get away with a team of just one or two developers and hope for the best. There&amp;rsquo;s absolutely nothing wrong with that. But if you have the funds to add new members to your team, don&amp;rsquo;t just go with more developers. Developers aren&amp;rsquo;t generally very good at designing products people want to use &amp;ndash; they are good at implementing well-specced features. That leads to the first non-developer position I recommend filling in:&lt;/p&gt;
&lt;h4 id=&#34;product-manager&#34;&gt;Product Manager&lt;/h4&gt;
&lt;p&gt;A good product manager is worth their weight in gold (OK maybe not that much given the price of gold these days, but you get the idea). The product manager&amp;rsquo;s job involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Knowing the product inside out. She should be the person everyone goes to if they have a product question.&lt;/li&gt;
&lt;li&gt;Providing input on which features to develop first. This requires a thorough understanding of company objectives and user expectations.&lt;/li&gt;
&lt;li&gt;Dividing new feature developments into manageable chunks with the help of the software tech lead. Great product managers can also make suggestions about which parts of the features should &lt;em&gt;not&lt;/em&gt; be developed right away and can be safely deferred.&lt;/li&gt;
&lt;li&gt;Writing detailed specifications for each new feature to develop.&lt;/li&gt;
&lt;li&gt;Manually testing the newly developed features to see if they conform to the specification.&lt;/li&gt;
&lt;li&gt;Writing documentation about features.&lt;/li&gt;
&lt;li&gt;Helping with release planning.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;ux-designer&#34;&gt;UX Designer&lt;/h4&gt;
&lt;p&gt;Good product managers can double as UX designers to a certain extent especially in the early days of a startup, but as soon as you get the funds to hire a proper UX designer, don&amp;rsquo;t think &amp;ndash; hire one. A good UX designer can design user interfaces that look nice &lt;em&gt;and&lt;/em&gt; a joy to use. From a user&amp;rsquo;s perspective your product is simply what&amp;rsquo;s visible to her. A user can&amp;rsquo;t be expected to know how things work in the background. If she sees a button on the screen and presses it, she expects it to behave as she thinks it &lt;em&gt;should&lt;/em&gt;. A good UX designer can make your product behave as the user expects it to, and that leads to happy users and fewer support requests.&lt;/p&gt;
&lt;p&gt;A UX designer&amp;rsquo;s job involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Designing screens &lt;em&gt;and&lt;/em&gt; deciding on how the user navigates from one screen to another.&lt;/li&gt;
&lt;li&gt;Doing usability tests preferably on prototypes in software like Figma &lt;em&gt;before&lt;/em&gt; developers implement a feature.&lt;/li&gt;
&lt;li&gt;Iteratively improving existing designs by observing actual user interactions with the product.&lt;/li&gt;
&lt;li&gt;Working closely with the software developers and modifying the designs if they are technically difficult to implement. Not all designs are practical to implement, and changes may be necessary to reduce development time as long as they don&amp;rsquo;t negatively affect user experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;qa-engineer&#34;&gt;QA Engineer&lt;/h4&gt;
&lt;p&gt;Once you have your product manager and UX designer in place, it&amp;rsquo;s time to hire your first QA engineer. Sure, software developers can write their own automated tests, but their main job is to implement features. A dedicated QA engineer can be much more productive than developers in writing tests, especially the all important integration tests that touch multiple features. A QA engineer has a birds-eye view of your code base while developers often focus on individual parts of the code.&lt;/p&gt;
&lt;p&gt;A QA Engineer&amp;rsquo;s job involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Having a good understanding of how each feature under test is supposed to work.&lt;/li&gt;
&lt;li&gt;Writing &lt;em&gt;maintainable&lt;/em&gt; test cases that make sure that your code base functions correctly. Using a Behavior Driven Development (BDD) tool like Cucumber helps design test flows that can be modified easily.&lt;/li&gt;
&lt;li&gt;Making small modifications to your code base such as adding a class attribute to an HTML tag so that a feature is easier to test. When bigger modifications are necessary, asking software developers to write code more suited to testing.&lt;/li&gt;
&lt;li&gt;Keeping test suites up to date. It&amp;rsquo;s normal for some tests to fail for reasons other than bugs, but those tests should be fixed as soon as possible, and the number of failing tests should be kept to a minimum.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;software-tech-lead&#34;&gt;Software Tech Lead&lt;/h4&gt;
&lt;p&gt;This role is usually given to the most experienced software developer on the team, but experience alone isn&amp;rsquo;t sufficient. The tech lead must have some &amp;ldquo;soft&amp;rdquo; skills in order to effectively prioritize and distribute work on her team. If your tech lead doesn&amp;rsquo;t have enough hard or soft skills, she can receive mentoring from a more experienced software developer if you have one like the CTO or a tech lead on another team.&lt;/p&gt;
&lt;p&gt;A tech lead&amp;rsquo;s job involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deciding on the architecture and technologies to use for the features to develop. A good tech lead is practical &amp;ndash; she chooses simple architectures over complex ones and prefers battle-tested technologies over the latest buzzword ones.&lt;/li&gt;
&lt;li&gt;Prioritizing work in coordination with the product manager.&lt;/li&gt;
&lt;li&gt;Making &lt;em&gt;quick&lt;/em&gt; estimates on how much she thinks each feature would take to complete taking into account the inevitable bugs that would need to be fixed.&lt;/li&gt;
&lt;li&gt;Writing code that deals with core functionality.&lt;/li&gt;
&lt;li&gt;Coming up with suggestions to improve existing code and processes.&lt;/li&gt;
&lt;li&gt;Helping team members with their technical and personal issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;software-developer&#34;&gt;Software Developer&lt;/h4&gt;
&lt;p&gt;In my experience, for small teams, having an experienced software tech lead supported by several junior software developers tends to work well. Junior developers often need assistance from the tech lead or from each other, so I have them work in groups of two or three, and make it clear that they should help each other as much as possible. The technical hurdles to cross can be overwhelming for junior developers especially when they are fresh out of school. Working in groups make them feel that they aren&amp;rsquo;t alone and it&amp;rsquo;s OK to seek help when they&amp;rsquo;re stuck.&lt;/p&gt;
&lt;p&gt;For software developers, it isn&amp;rsquo;t important what they know as long as they have the basics covered &amp;ndash; what&amp;rsquo;s important is that they are highly motivated to learn. It also helps if they&amp;rsquo;re quick learners.&lt;/p&gt;
&lt;h4 id=&#34;devops-engineer&#34;&gt;DevOps Engineer&lt;/h4&gt;
&lt;p&gt;Strictly speaking, this position isn&amp;rsquo;t necessary if your product has few users, doesn&amp;rsquo;t require large servers to function, and your users are fine with the occasional downtime if it&amp;rsquo;s not too long. If you have a software developer on your team who can &lt;em&gt;willingly&lt;/em&gt; devote some of her time to managing your servers then you can probably get away without hiring a DevOps engineer until you really need one.&lt;/p&gt;
&lt;p&gt;A DevOps Engineer&amp;rsquo;s job involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Having separate test and production servers for your product.&lt;/li&gt;
&lt;li&gt;Having &amp;ldquo;one-click&amp;rdquo; software deploy &lt;em&gt;and&lt;/em&gt; rollback processes.&lt;/li&gt;
&lt;li&gt;Backing up all critical data such as databases and git repos regularly, and testing those backups actually work.&lt;/li&gt;
&lt;li&gt;Monitoring servers for abnormal conditions and taking corrective actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a final note, for a startup it&amp;rsquo;s especially important to hire people who can take the initiative and work under minimal supervision. People who are accustomed to large corporate environments where it&amp;rsquo;s common to have a career ladder and strict processes to follow may not do well at a startup.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Three Power Amp Fan Mods</title>
      <link>https://lackofimagination.org/2023/09/three-power-amp-fan-mods/</link>
      <pubDate>Fri, 29 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/09/three-power-amp-fan-mods/</guid>
      <description>&lt;p&gt;I use pro audio power amps in my home theater. They provide tons of power for little money. There is one major downside though. Most models feature noisy fans, and for a good reason &amp;ndash; pro audio power amps are often run at their limits, and many manufacturers prefer to err on the side of caution. For home use though, you can usually get away with replacing the built-in fans with quieter ones as long as the amp &lt;em&gt;isn&amp;rsquo;t&lt;/em&gt; housed inside closed spaces with poor or no ventilation.&lt;/p&gt;
&lt;p&gt;Quiet fans move less air, but movies rarely demand the level of cooling required in playing very loud, very compressed music for hours at a time, which is exactly the thing that taxes power amps the most.&lt;/p&gt;
&lt;p&gt;I have done three power amp fan mods so far, and each one was different. Let&amp;rsquo;s start with the easiest one first:&lt;/p&gt;
&lt;p&gt;The Behringer iNuke NU6000DSP, a class D amp with built-in DSP. It can do about 2 x 2,000W into 4 ohm short-term, an inexpensive amp well suited for subwoofer duty.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/behringer_inuke_nu6000dsp_front.jpg&#34;  alt=&#34;iNuke nu6000DSP, a class D amp with built-in DSP. It can do about 2 x 2,000W short term into 4 ohm.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;iNuke nu6000DSP, a class D amp with built-in DSP. It can do about 2 x 2,000W short term into 4 ohm.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;The NU6000DSP uses dual 80 mm 12V fans. For this one, I used USB powered 80 mm fans and mounted the fans from the outside. Internal mounting meant drilling holes in the case, which I preferred not to. The USB connector is plugged into the AVR, and the fans start spinning when the AVR is turned on.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/behringer_inuke_nu6000dsp_usb_fan_mod.jpg&#34;  alt=&#34;iNuke nu6000DSP USB fan replacement.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;iNuke nu6000DSP USB fan replacement.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Easy&lt;/p&gt;
&lt;p&gt;Up next, the Behringer iNuke NU3000DSP, smaller brother of iNuke NU6000DSP, is again a class D amp with built-in DSP. It can do about 2 x 500W into 4 ohm. When bridged, it can produce as much as 2,000W short-term into 4 ohm.&lt;/p&gt;
&lt;p&gt;The NU3000DSP looks exactly like the NU6000DSP, but is shorter in depth. It uses an 80 mm 12V fan covered by a black cardboard shroud, which is lightly glued to the fan. The shroud appears to be designed to concentrate air flow to four transistors with small yellow heat-sinks. Once the shroud is removed, replacing the existing fan with a quieter version is easy. The fan connector needs to be fixed in place with a dab of hot glue though.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/behringer_inuke_nu3000dsp_inside.jpg&#34;  alt=&#34;iNuke NU3000DSP&amp;#39;s fan covered by a black cardboard shroud.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;iNuke NU3000DSP&amp;#39;s fan covered by a black cardboard shroud.&lt;/span&gt;
  
&lt;/div&gt;




&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/behringer_inuke_nu3000dsp_fan_mod.jpg&#34;  alt=&#34;iNuke NU3000DSP quiet fan replacement.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;iNuke NU3000DSP quiet fan replacement.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Easy&lt;/p&gt;
&lt;p&gt;The fan mod that required the most effort is a Behringer KM750, a traditional class AB amp. I use this one for my center channel speaker. It can do about 2 x 180W RMS into 4 ohm.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/behringer_km750_front.png&#34;  alt=&#34;Behringer KM750, a traditional class AB power amp that can do 2 x 180W RMS into 4 ohm.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Behringer KM750, a traditional class AB power amp that can do 2 x 180W RMS into 4 ohm.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;The KM750 has a 90 mm 24V fan mounted on an internal heat sink. As the heat sink is wide enough, I decided to use a 120 mm fan, which can spin slower (and quieter), but still move quite a bit of air. So, how do you mount a 120 mm fan to a heat sink with holes drilled for a 90 mm fan?&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/behringer_km750_original_fan.jpg&#34;  alt=&#34;Behringer KM750 uses a 90 mm 24V fan with a 2-pin connector.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Behringer KM750 uses a 90 mm 24V fan with a 2-pin connector.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;I had four options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mount the fan by using just one hole and hope the fan wouldn&amp;rsquo;t move over time.&lt;/li&gt;
&lt;li&gt;Drill and tap new holes in the heat sink.&lt;/li&gt;
&lt;li&gt;Buy an adapter.&lt;/li&gt;
&lt;li&gt;3D print an adapter.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I happen to have a 3D printer, and decided to print a 3D model I found online. I had to print it slightly thinner though. Otherwise it wouldn&amp;rsquo;t fit under the top cover of the amp.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/120mm_to_90mm_fan_adapter.jpg&#34;  alt=&#34;3D printed 120 mm to 90 mm fan adapter.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;3D printed 120 mm to 90 mm fan adapter.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;Since most quiet fans are 12V, an inexpensive buck converter is necessary to lower the 24V provided by the amp to 12V or less. In the end, the 120 mm fan wasn&amp;rsquo;t quiet enough at 12V, so I lowered the voltage to less than 10V until the fan noise was acceptable. Even in cases when you don&amp;rsquo;t need a buck converter, they can come handy to lower fan noise to exactly the level you want.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/power-amp-fan-mods/behringer_km750_120mm_fan_installed.jpg&#34;  alt=&#34;120 mm fan installed. A buck converter lowers the voltage to less than 12V.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;120 mm fan installed. A buck converter lowers the voltage to less than 12V.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;In the picture above, the buck converter can be seen on the bottom-right corner mounted on a suitable spot. I didn&amp;rsquo;t feel like drilling holes in the bottom of the case. There was already a screw hole for one of the feet, so I used that screw, a 3D-printed piece to mount the buck converter and some hot glue to hold everything in place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Medium&lt;/p&gt;
&lt;p&gt;&amp;hellip;and that&amp;rsquo;s a wrap. Three different amps, three different ways to do fan mods.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; Working on electronics connected to AC power can &lt;em&gt;kill&lt;/em&gt; you. If you don&amp;rsquo;t know what you are doing, please don&amp;rsquo;t.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>CNC Router Control Box Wiring</title>
      <link>https://lackofimagination.org/2023/09/cnc-router-control-box-wiring/</link>
      <pubDate>Sun, 24 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/09/cnc-router-control-box-wiring/</guid>
      <description>&lt;p&gt;A few months ago, I bought an entry-level CNC router from a local supplier, knowing full well that I would probably need to modify it extensively. The price of the machine was right, and in case I decide to build a better one on my own, this machine would be a good starting point to learn how all the parts are connected together.&lt;/p&gt;
&lt;p&gt;The machine performed OK as long as I didn&amp;rsquo;t push it hard. I added drag chains to the Y-axis to prevent the step motor cables from moving all over the place while the machine was running. I also added a dust boot which was not only essential for dust extraction, but also freed me from standing right next to the machine holding a vacuum hose while it was running.&lt;/p&gt;
&lt;p&gt;After these modifications, connecting a simple height probe that would be used to automatically calculate the distance of the router bit to the waste board seemed easy enough &amp;ndash; just two wires connected to the control board located inside the CNC router control box. But, try as I might, I couldn&amp;rsquo;t get my hands to the red control board even after removing some of the cabling. It was very cramped inside. I also needed to reduce the current to the Y axis motors (they got too hot), but I couldn&amp;rsquo;t access the jumpers on the step motor drivers either. There were other things that I wasn&amp;rsquo;t happy with the control box &amp;ndash; there were no jacks outside the box to connect cables. All cables went straight inside through bushings.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/old_cnc_control_box_inside.jpg&#34;  alt=&#34;Inside the original cramped CNC router control box.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Inside the original cramped CNC router control box.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;So, I thought it wouldn&amp;rsquo;t be too hard to buy a bigger box and move everything from the old box to the new one, making a few improvements along the way. A CNC router control box is relatively simple to make. It consists of a power supply, step motor drivers and a control board. I replaced the generic brand 12V power supply with a Mean Well 24V power supply I had lying around. The extra voltage should help the motors generate more torque at higher speeds.&lt;/p&gt;
&lt;p&gt;I changed the component layout so that the control board&amp;rsquo;s USB input could be accessed directly from the outside. I also placed the step motor drivers between the power supply and control board to separate signal wires coming from the control board and power wires coming from the power supply as much as possible. Finally, I added cable ducts with slits to keep the wiring neat. Can&amp;rsquo;t recommend these ducts enough &amp;ndash; they make wire management very easy.&lt;/p&gt;
&lt;p&gt;The pictures below show the testing of the components outside the box, and a close-up view of the step motor driver wiring.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/better_organized_cnc_router_control_box.jpg&#34;  alt=&#34;The new electronics layout was tested outside the box.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The new electronics layout was tested outside the box.&lt;/span&gt;
  
&lt;/div&gt;




&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/cnc_router_step_motor_drivers_closeup.jpg&#34;  alt=&#34;Signal wires come from one side and power wires come from the other side through wire ducts.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Signal wires come from one side and power wires come from the other side through wire ducts.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;For outside connections, I used 4-pole Speakon connectors for step motor cables, and 3-pole XLR connectors for things like height probes and limit switches. These connectors are normally used in professional audio, but I really like the easy locking mechanism of these types of audio connectors.&lt;/p&gt;
&lt;p&gt;In the picture below, only the AC power socket and the Speakon jacks are installed. With the &amp;ldquo;eyes and mouth&amp;rdquo;, the box kind of looks cute though, don&amp;rsquo;t you think? :)&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/outside_view_of_the_new_cnc_router_control_box.jpg&#34;  alt=&#34;Outside view of the new CNC router control box with Speakon jacks installed.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Outside view of the new CNC router control box with Speakon jacks installed.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;The picture below shows the components installed in the box. There&amp;rsquo;s plenty of space now for future expansion. It&amp;rsquo;s probably a good idea to install a fan to keep the components cool especially if the machine is expected to run long hours.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/inside_the_new_cnc_router_control_box.jpg&#34;  alt=&#34;Inside the new CNC router control box. No bushings! :)&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Inside the new CNC router control box. No bushings! :)&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Tools of the trade:&lt;/strong&gt; Wire stripper, crimpers, and a cordless drill with the necessary bits. I especially like the look of the big step bit installed in the cordless drill. Looks like a ray gun :) You can buy the cheapest step drill bit you can find. They are perfectly adequate for drilling plastic, which is all I needed here.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/wire_crimpers.jpg&#34;  alt=&#34;Wire crimpers.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Wire crimpers.&lt;/span&gt;
  
&lt;/div&gt;




&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/step_drill_bit.jpg&#34;  alt=&#34;Step drill bit is great for circular cutouts on plastic.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Step drill bit is great for circular cutouts on plastic.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;Finally, the finished CNC router control box with four step motor connectors and one height probe connected.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/cnc-router/finished_cnc_router_control_box.jpg&#34;  alt=&#34;The new CNC router control box standing under the CNC router table.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;The new CNC router control box standing under the CNC router table.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; Working on electronics connected to AC power can &lt;em&gt;kill&lt;/em&gt; you. If you don&amp;rsquo;t know what you are doing, please don&amp;rsquo;t. Also, keep in mind that I&amp;rsquo;m a hobbyist, but I did consult professionals including an electrical engineer friend for my CNC control box work.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Three Stages of Software Development</title>
      <link>https://lackofimagination.org/2023/09/three-stages-of-software-development/</link>
      <pubDate>Wed, 20 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/09/three-stages-of-software-development/</guid>
      <description>&lt;p&gt;There are a lot of things to consider before starting to write code: Which software libraries to choose? Use SQL or NoSQL? What would the database schema look like? Would we need a caching system to handle heavy traffic? How should the code be structured? And so on&amp;hellip;&lt;/p&gt;
&lt;p&gt;These questions are certainly important, but we live in an era of fast-changing and often ill defined user requirements. The conventional software development model of &lt;em&gt;plan &amp;gt; code &amp;gt; test&lt;/em&gt; can more often than not fail to produce software that meets user requirements in a reasonable amount of time. One alternative is a three-stage process that allows us to iteratively learn what the users really want, and if all goes well develop better software faster.&lt;/p&gt;
&lt;h4 id=&#34;stage-1-make-it-work&#34;&gt;Stage 1: Make it Work&lt;/h4&gt;
&lt;p&gt;This is similar to Stephen King&amp;rsquo;s approach to writing the first draft of a new book. King tries to finish the first draft as quickly as possible, with little concern for sentence structure or vocabulary. The important thing is to create a draft that, though rough around the edges, can be refined and polished later. Similarly, in software development the aim is to produce a prototype that can be shared with users without worrying about code quality or technical architecture very much. It&amp;rsquo;s acceptable for some parts to be flawed, and performance may not be optimal at this stage &amp;ndash; these concerns can be addressed later.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a saying that the best code is no code. Depending on user feedback, we may find that we don&amp;rsquo;t need to work on features we initially thought would be challenging, or we may uncover new areas to work on that we haven&amp;rsquo;t anticipated earlier.&lt;/p&gt;
&lt;h4 id=&#34;stage-2-make-it-right&#34;&gt;Stage 2: Make it Right&lt;/h4&gt;
&lt;p&gt;After the first stage is complete, it&amp;rsquo;s time to fix all the important bugs and address any shortcomings of the product. During this stage, we should make the user interface easier to use, ensure that there are automated tests to check all features work as expected, display proper error messages (no more &amp;ldquo;unknown errors&amp;rdquo; please), and address all security concerns. This is usually the stage where the majority of development time is spent. Good luck!&lt;/p&gt;
&lt;h4 id=&#34;stage-3-make-it-fast&#34;&gt;Stage 3: Make it Fast&lt;/h4&gt;
&lt;p&gt;This is the stage I personally find the most fun. At this stage, we should have a software product that has been shaped by user feedback and is functioning correctly. However, we may start encountering or have already encountered performance issues. So, it&amp;rsquo;s finally time to optimize.&lt;/p&gt;
&lt;p&gt;Many software developers have a tendency to do performance optimizations early in development, but there&amp;rsquo;s a good reason to delay optimizations until the final stage: predicting what would be slow within a complex software system before it reaches a certain level of maturity and is tested by actual users is hard even for experienced software developers. Also, a relatively mature software product is essential for establishing a baseline for performance before making any changes to the code.&lt;/p&gt;
&lt;p&gt;In my experience, it&amp;rsquo;s more efficient to achieve perfection step by step, rather than trying to write beautiful and performant code from day one. So, please try to ignore the perfectionist voice in the back of your head, at least early on.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Subwoofers for Home Use: An In-depth Guide</title>
      <link>https://lackofimagination.org/2023/02/subwoofers-for-home-use-an-in-depth-guide/</link>
      <pubDate>Sun, 12 Feb 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2023/02/subwoofers-for-home-use-an-in-depth-guide/</guid>
      <description>&lt;p&gt;Subwoofers are probably the most misunderstood and difficult-to-integrate components of a music or home theater system. However, when integrated properly, they have the potential to elevate your speakers to a whole new level. Over the years, I&amp;rsquo;ve bought and built quite a few subwoofers, and I&amp;rsquo;d like to share what I&amp;rsquo;ve learned about them with you.&lt;/p&gt;
&lt;p&gt;The first few sections of this guide cover the basics, such as how to choose the right subwoofer(s) and how to make them sound good (they aren&amp;rsquo;t exactly plug-and-play). Later sections discuss building your own subwoofers for a fraction of the cost of commercial units. Finally, for endgame deep bass reproduction, you might want to read the section on the double bass array (DBA).&lt;/p&gt;
&lt;h1 id=&#34;table-of-contents&#34;&gt;Table of Contents&lt;/h1&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#first-things-first-whats-a-subwoofer&#34;&gt;First things first, what&amp;rsquo;s a subwoofer?&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#types-of-subwoofers&#34;&gt;Types of subwoofers&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#what-to-look-for-in-a-subwoofer&#34;&gt;What to look for in a subwoofer?&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#how-many-subwoofers&#34;&gt;How many subwoofers?&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#how-to-setup-subwoofers&#34;&gt;How to setup subwoofer(s)&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#where-to-place-your-subwoofers&#34;&gt;Where to place your subwoofer(s)&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#where-to-sit&#34;&gt;Where to sit&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#the-crossover-frequency&#34;&gt;The Crossover Frequency&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#room-correction&#34;&gt;Room Correction&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#diy-subwoofers&#34;&gt;DIY Subwoofers&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#how-to-choose-a-driver&#34;&gt;How to choose a driver&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#box-volume&#34;&gt;Box volume&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#amplifier-selection&#34;&gt;Amplifier selection&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#building-a-box&#34;&gt;Building a box&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#measuring-and-correcting-output&#34;&gt;Measuring and correcting output&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#end-game-double-bass-array&#34;&gt;End Game: Double Bass Array&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;

&lt;h4 id=&#34;first-things-first-whats-a-subwoofer&#34;&gt;First things first, what&amp;rsquo;s a subwoofer?&lt;/h4&gt;
&lt;p&gt;The image of a large black box housing a big speaker driver that pumps out deep bass is probably the first thing that comes to mind. A subwoofer&amp;rsquo;s purpose is to complement the main speakers, many of which struggle to reproduce the lower octaves of music and sound effects at satisfactory levels.&lt;/p&gt;
&lt;h4 id=&#34;types-of-subwoofers&#34;&gt;Types of subwoofers&lt;/h4&gt;
&lt;p&gt;For home use, the most popular types are sealed and ported (also known as vented). Sealed subwoofers have only the driver that produces bass while ported subs have one or more vents usually in the shape of a circle or rectangle that produce bass in addition to the driver.&lt;/p&gt;
&lt;p&gt;Sealed subwoofers are compact. Although large sealed subwoofers exist, a sealed subwoofer can be made only slightly larger than the driver dimensions (imagine a 13-inch cube housing a 12-inch driver).&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/svs_sb1000_pro_compact_sealed_subwoofer.jpg&#34;  alt=&#34;A compact sealed 12&amp;#34; subwoofer. Image © SVS&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;A compact sealed 12&amp;#34; subwoofer. Image © SVS&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Ported subwoofers are generally quite a bit larger, but they can produce significantly more output &amp;ndash;up to 10 dB&amp;ndash; within a certain frequency range compared to sealed subwoofers. However, some may suffer from port noise (also known as chuffing) when pushed to their limits.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/mfw15_turbo_15_slot_ported_subwoofer.jpg&#34;  alt=&#34;A rather nice looking 15&amp;#34; slot ported subwoofer. Not all ported subwoofers look this good. In fact, featureless black boxes are the norm. Image © Seaton Sound, Inc.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;A rather nice looking 15&amp;#34; slot ported subwoofer. Not all ported subwoofers look this good. In fact, featureless black boxes are the norm. Image © Seaton Sound, Inc.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;h4 id=&#34;what-to-look-for-in-a-subwoofer&#34;&gt;What to look for in a subwoofer?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Box dimensions:&lt;/strong&gt; It depends on where your priorities lie. If compact size is important to you, then large ported subwoofers are probably out of the question. That doesn&amp;rsquo;t mean that all ported subwoofers are large &amp;ndash; there are relatively compact ported subwoofers, but they have to sacrifice some deep bass output in order to fit in a compact cabinet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Driver diameter:&lt;/strong&gt; All other things being equal, as the driver diameter gets larger, the bass output produced by the driver increases. Subwoofer drivers typically come in nominal diameters of 8&amp;quot;, 10&amp;quot;, 12&amp;quot;, 15&amp;quot; and 18&amp;quot; (20, 25, 30, 38, 46 cm respectively). The most popular diameters for home use are 10&amp;quot; and 12&amp;quot;. A subwoofer can have more than one driver, but most have one.&lt;/p&gt;



&lt;br&gt;
&lt;div class=&#34;figure &#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/subwoofer_sizes_10_12_15.png&#34;  alt=&#34;15&amp;#34;, 12&amp;#34; and 10&amp;#34; drivers from a car subwoofer driver line. Some home subwoofer manufacturers use car drivers, and they are also a popular choice for DIY subwoofer builders. More on that later.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;15&amp;#34;, 12&amp;#34; and 10&amp;#34; drivers from a car subwoofer driver line. Some home subwoofer manufacturers use car drivers, and they are also a popular choice for DIY subwoofer builders. More on that later.&lt;/span&gt;
  
&lt;/div&gt;

&lt;p&gt;A good rule of thumb is that as you increase the size of the driver, you get about twice as much bass output. For example, a 12&amp;quot; driver has roughly double the output of a 10&amp;quot; driver, assuming both drivers have similar technical parameters other than surface area, and the same amount of amplifier power is used. However, there are exceptions. Some high-performance 10&amp;quot; drivers can outperform an average 12&amp;quot; driver, but these can be expensive and often require substantial amplifier power to reach their full potential.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amplifier power:&lt;/strong&gt; After driver size, the next important factor to consider is the available amplifier power. Most commercial home subwoofers come with built-in amplifiers. The amplifier needs to provide enough power for the driver to achieve satisfactory output levels. It&amp;rsquo;s wise to disregard any peak power ratings, as they are often exaggerated and difficult to compare across different brands. What matters is RMS power (also known as continuous power), preferably provided with a distortion rating (like 1% THD) to allow for accurate comparisons.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/psb_subseries_250_rear_plate_amp.jpg&#34;  alt=&#34;A home subwoofer usually has a built-in amplifier to supply power to the driver.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;A home subwoofer usually has a built-in amplifier to supply power to the driver.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;There&amp;rsquo;s no need to sweat over small differences in amplifier power. For example a 25% difference may look big on paper when comparing a 400W RMS amplifier to a 500W RMS amplifier, but it only amounts to 1 db more output all other things being equal, which is so small that it&amp;rsquo;s hard to tell the difference in practice. For a meaningful output difference, an amplifier should be able to provide twice the power which is equivalent to 3 db of additional output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Frequency response:&lt;/strong&gt; Finally, take a look at the +/- 3db frequency response specification of the subwoofer. A subwoofer that can authoritatively reach down to 25 Hz or lower is ideal. However, frequency response alone isn&amp;rsquo;t very telling. Nowadays, many sealed subwoofers utilize digital signal processing (DSP) or other means to enhance lower frequency output at lower volumes. If the driver lacks sufficient surface area and the amplifier power is limited, a subwoofer&amp;rsquo;s ability to reach low frequencies doesn&amp;rsquo;t matter much, as these frequencies won&amp;rsquo;t be loud enough to hear or feel at higher volumes.&lt;/p&gt;
&lt;h4 id=&#34;how-many-subwoofers&#34;&gt;How many subwoofers?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;How about zero?&lt;/strong&gt;&lt;br&gt;
It might seem odd to suggest going without a subwoofer in a guide about home subwoofers, but if you have terrible room acoustics that are difficult to manage, it might actually be a good idea to go without one. Contrary to common belief, boomy bass isn&amp;rsquo;t always caused by cheap subwoofers. More often than not, the main culprit is poor room acoustics &amp;ndash; walls made of brick or concrete and minimal furnishings can create severe resonance issues.&lt;/p&gt;
&lt;p&gt;If you clearly hear an echo when you clap your hands inside a room, you will likely experience boomy bass with a subwoofer in that space regardless of the subwoofer&amp;rsquo;s quality. While there are ways to address some acoustical issues, not all solutions are practical. However, don&amp;rsquo;t get discouraged. Just be aware that in some cases, it might be better to have no subwoofer at all than to have one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One subwoofer placed right&lt;/strong&gt;&lt;br&gt;
If you are going to have only one subwoofer, like most people, achieving the most even response from your subwoofer is all about placement. There are really only two optimal locations for a single subwoofer to accomplish this (more on this later).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Going dual: Buy two get two free&lt;/strong&gt;&lt;br&gt;
If you have the funds and space for two subwoofers, having dual subwoofers offers several advantages over a single subwoofer, assuming their placement in the room is correct:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quadruple the output over a single subwoofer if they are placed relatively close to each other. You literally get two additional subwoofers worth of output for free, thanks to a physical phenomenon known as mutual coupling of sound waves.&lt;/li&gt;
&lt;li&gt;Consistent bass output across a horizontal plane. In other words, no matter where you sit on the sofa, the bass will sound about the same.&lt;/li&gt;
&lt;li&gt;Instead of one large subwoofer that is difficult to place or hide, two smaller subwoofers can be used.&lt;/li&gt;
&lt;li&gt;Two subwoofers can function optimally in many more locations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;More than two subwoofers?&lt;/strong&gt;&lt;br&gt;
While there is no upper limit to the number of subwoofers you can use, for most people, having two subwoofers hits the sweet spot. If you simply need more bass output, you can colocate subwoofers (a fancy term for placing multiple subwoofers in close proximity), and they will act as one large subwoofer.&lt;/p&gt;
&lt;p&gt;You can also have more than two subwoofers distributed across the room, but they require careful measurements to function properly. If you are curious about non-colocated subwoofer setups with more than two subwoofers, head over to the &lt;a href=&#34;#end-game-double-bass-array&#34;&gt;double bass array&lt;/a&gt; section.&lt;/p&gt;
&lt;h4 id=&#34;how-to-setup-subwoofers&#34;&gt;How to setup subwoofer(s)&lt;/h4&gt;
&lt;p&gt;It&amp;rsquo;s extremely unlikely that randomly placing a subwoofer will result in good sound. Subwoofers require some effort to sound their best.&lt;/p&gt;
&lt;h5 id=&#34;where-to-place-your-subwoofers&#34;&gt;Where to place your subwoofer(s)&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;One subwoofer&lt;/strong&gt;&lt;br&gt;
Try to avoid corner placement as it will produce uneven bass across the room. If possible, place your subwoofer along the front wall, preferably right in the center. If the center of the front wall isn&amp;rsquo;t available, you can try placing your subwoofer along the center of the rear wall.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/single_subwoofer_center_front_or_rear.png&#34;  alt=&#34;Center front or rear wall placement is ideal for a single subwoofer.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Center front or rear wall placement is ideal for a single subwoofer.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Two subwoofers&lt;/strong&gt;&lt;br&gt;
Having two subwoofers offers a lot of flexibility with placement. They can be placed in each corner of your front wall. You can also move them closer toward the center of the wall as long as each subwoofer is roughly an equal distance from the side walls. Placing the subwoofers at locations of 1/4 the width of the front wall is recommended to achieve the smoothest response. For example, if your front wall is 4 meters wide, try placing your subwoofers about 1 meter away from the side walls.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/dual_subwoofers_corner_front.png&#34;  alt=&#34;Dual subwoofers can be placed symmetrically along the front wall for a smooth bass response.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Dual subwoofers can be placed symmetrically along the front wall for a smooth bass response.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;h5 id=&#34;where-to-sit&#34;&gt;Where to sit&lt;/h5&gt;
&lt;p&gt;If possible, avoid sitting close to walls (especially the rear wall), where bass can become overwhelmingly intense in a negative way. Sitting in the middle of the room isn&amp;rsquo;t a good idea either, as this is where bass energy is typically at its weakest.&lt;/p&gt;
&lt;h5 id=&#34;the-crossover-frequency&#34;&gt;The Crossover Frequency&lt;/h5&gt;
&lt;p&gt;The crossover frequency determines at which point your main speakers start to hand off bass reproduction to your subwoofer(s). An 80 Hz crossover frequency is usually recommended, but if your speakers are particularly lacking in bass, you might want to increase this to 100 or 120 Hz for a smoother transition. Virtually all AV receivers (AVRs) have built-in crossover capabilities. If you are using an AVR to set the crossover, it&amp;rsquo;s best to disable the crossover setting on your subwoofer(s) or set it to the maximum setting possible.&lt;/p&gt;
&lt;h5 id=&#34;room-correction&#34;&gt;Room Correction&lt;/h5&gt;
&lt;p&gt;Once the subwoofer placement is optimal and you are ideally not sitting too close to the rear wall, there is one more step left to enjoy smooth bass reproduction in your room: applying room correction. This is necessary to address room resonances that can&amp;rsquo;t be solely fixed by subwoofer placement.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/denon_avr-s760h.jpg&#34;  alt=&#34;Many modern AV receivers have automatic room correction capabilities. Image © Sound United, LLC.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Many modern AV receivers have automatic room correction capabilities. Image © Sound United, LLC.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;If you own a modern AV receiver, it&amp;rsquo;s a good idea to try out your AVR&amp;rsquo;s automatic room correction capabilities. Many AVRs produced in the last decade support some form of automatic room correction, and they generally work in a similar way, although the results may vary depending on the quality of the room correction algorithm used (some are very basic) and the severity of the acoustic issues.&lt;/p&gt;
&lt;p&gt;You should place the calibration microphone that comes with the AVR in one or more listening positions, preferably attached to a tripod, and allow the AVR to take measurements by playing test tones for each speaker, including your subwoofer. After the measurements are completed, corrections are applied at certain frequencies to reduce the audibility of resonances in your listening position. Although it&amp;rsquo;s called room correction, the effect of the corrections is often limited to the small listening area where the calibration microphone is placed.&lt;/p&gt;
&lt;p&gt;For advanced users who have a good measurement microphone (you can probably get away with the AVR&amp;rsquo;s microphone although some caveats apply), there is a powerful free software application called &lt;a href=&#34;https://www.roomeqwizard.com&#34;&gt;REW&lt;/a&gt; (Room EQ Wizard) that can be used to measure your subwoofer&amp;rsquo;s response. Then, you can apply corrections using the parametric EQ capabilities available on your computer or an external device called a DSP. More on that in the DIY section.&lt;/p&gt;
&lt;h4 id=&#34;diy-subwoofers&#34;&gt;DIY Subwoofers&lt;/h4&gt;
&lt;p&gt;Granted, DIY (Do-It-Yourself) isn&amp;rsquo;t for everyone, but for those interested in building their own subwoofers and making their own acoustic measurements, the potential rewards are significant. You can achieve high-quality bass reproduction for a fraction of the cost of an equivalent commercial subwoofer setup and professional calibration. Not to mention the personal satisfaction of building something yourself that will be put to good use for years to come.&lt;/p&gt;
&lt;p&gt;Building a subwoofer isn&amp;rsquo;t really hard. It can be done with little to no woodworking skills and minimal tools. Having had no prior experience, I built my first subwoofer using nothing more than a power drill, a jigsaw, and a few wood clamps. Since then, I&amp;rsquo;ve built many subwoofers over the years, and although the later ones look better, they&amp;rsquo;ve all functioned well.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy_subwoofers_using_car_drivers.jpg&#34;  alt=&#34;Building a subwoofer can be done with little to no woodworking skills.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Building a subwoofer can be done with little to no woodworking skills.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;A subwoofer consists of three components: one or more subwoofer drivers, an amplifier, and a box. Sourcing the driver(s) and amplifier(s) is relatively easy. The only remaining task is to build a box from MDF (Medium Density Fiberboard) or high-quality plywood. If you don&amp;rsquo;t have the tools to make the cuts, your local home improvement store will likely do the job for you for a small fee. You may also purchase ready-made subwoofer flat-packs online if they are available in your region. Once you have the pieces cut, they can simply be glued together with wood glue and held with a few clamps until the glue dries. More on box construction later.&lt;/p&gt;
&lt;h5 id=&#34;how-to-choose-a-driver&#34;&gt;How to choose a driver&lt;/h5&gt;
&lt;p&gt;Among DIY subwoofer builders, the driver of choice is often a car subwoofer driver for a number of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They are widely available and many are reasonably priced.&lt;/li&gt;
&lt;li&gt;They have the necessary moderate to high excursion capabilities to reproduce special sound effects like explosions at high volumes, a desired feature for home theaters.&lt;/li&gt;
&lt;li&gt;Many are designed to work in relatively compact enclosures, which is always a plus.&lt;/li&gt;
&lt;li&gt;Finally, many of them are tolerant of abuse since car subwoofer manufacturers know it all too well that car subwoofers are pushed beyond their limits quite often.&lt;/li&gt;
&lt;/ul&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/alpine_r2_w10d4_sundown_e8v5.jpg&#34;  alt=&#34;10&amp;#34; and 8&amp;#34; car subwoofer drivers with substantial motor structures.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;10&amp;#34; and 8&amp;#34; car subwoofer drivers with substantial motor structures.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Thiele/Small parameters&lt;/strong&gt;&lt;br&gt;
Most manufacturers provide the Thiele/Small (T/S) parameters for their subwoofers. These parameters can be used to objectively compare drivers and are also essential for determining the appropriate box size. For home theater use, the most important parameters are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maximum linear excursion (&lt;em&gt;Xmax&lt;/em&gt;), the maximum distance a subwoofer cone can travel in one way without gross distortion. Xmax is important because as you go down one octave such as from 40 hz to 20 hz, the subwoofer cone needs to travel 4 times the distance to have the same level of output.&lt;/li&gt;
&lt;li&gt;Usable surface area of the driver (&lt;em&gt;Sd&lt;/em&gt;), which is the area of the driver excluding parts of the surround and outer frame. The greater the surface area, the easier it is to produce bass.&lt;/li&gt;
&lt;li&gt;Resonant frequency (&lt;em&gt;Fs&lt;/em&gt;), which is the natural resonant frequency of the cone. This is the frequency where the driver is most efficient (outside of a box).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You will want Xmax to be as high as possible and &lt;em&gt;Fs&lt;/em&gt; to be as low as possible. &lt;em&gt;Sd&lt;/em&gt; does not change significantly across drivers of the same diameter, but it is very important because multiplying &lt;em&gt;Xmax&lt;/em&gt; by &lt;em&gt;Sd&lt;/em&gt; gives you &lt;em&gt;Vd&lt;/em&gt; (volume of displacement).&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ve probably heard the expression &amp;ldquo;there&amp;rsquo;s no replacement for displacement&amp;rdquo; used for car engines. Similarly for subwoofers, the larger the &lt;em&gt;Vd&lt;/em&gt; of a driver, the greater the deep bass output will be, all other factors being equal. For example, if the &lt;em&gt;Sd&lt;/em&gt; of two drivers are similar (they both have the same diameter), the one with twice the Xmax will produce twice the deep bass output, again assuming all other factors are equal. A large diameter driver with little Xmax and a small diameter driver with plenty of Xmax can produce about the same amount of deep bass, although you will likely need to provide the smaller driver with considerably more power to achieve this.&lt;/p&gt;
&lt;p&gt;Other T/S parameters such as &lt;em&gt;QTS&lt;/em&gt; and &lt;em&gt;Vas&lt;/em&gt; are also important in determining the optimum box size for a subwoofer. If you require a very compact box size without sacrificing too much deep bass output, make sure these two parameters are as low as possible.&lt;/p&gt;
&lt;h5 id=&#34;box-volume&#34;&gt;Box volume&lt;/h5&gt;
&lt;p&gt;The easiest subwoofer box to build is a sealed box with no ports. They can be made fairly compact and are tolerant of mistakes. There are free software applications such as &lt;a href=&#34;http://www.linearteam.org&#34;&gt;WinISD&lt;/a&gt; that can help you simulate the behavior of subwoofer enclosures after you enter your driver&amp;rsquo;s T/S parameters. They also let you see whether you exceed the excursion limits of a driver when you feed too much power to it, but for most drivers, unless the manufacturer recommends otherwise, you can simply use the following approximate sealed box volumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10&amp;quot; driver: 14 liters (0.5 cubic foot)&lt;/li&gt;
&lt;li&gt;12&amp;quot; driver: 28 liters (1 cubic foot)&lt;/li&gt;
&lt;li&gt;15&amp;quot; driver: 56 liters (2 cubic foot)&lt;/li&gt;
&lt;/ul&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/winisd_cone_excursion.png&#34;  alt=&#34;There are software applications that can help you simulate subwoofer behavior.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;There are software applications that can help you simulate subwoofer behavior.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;If you are going to put two drivers in the same box (such as in a dual opposed subwoofer build), you need to double the required volume. Please keep in mind that when calculating the volume of a box, you should use its &lt;em&gt;internal&lt;/em&gt; width, height, and depth dimensions.&lt;/p&gt;
&lt;h5 id=&#34;amplifier-selection&#34;&gt;Amplifier selection&lt;/h5&gt;
&lt;p&gt;While car subwoofer drivers are popular among DIY subwoofer builders, when it comes to amplifiers, inexpensive multi-kilowatt professional audio (pro audio) amplifiers are clearly the preferred choice. Pro audio amplifiers may not have the best signal-to-noise specifications, but they excel at providing substantial power, which is essential for power-hungry subwoofers. Some of them even have built-in DSP capabilities, which simplify setting crossovers and correcting response anomalies caused by room acoustics.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/crown_xls_1002_power_amplifier.png&#34;  alt=&#34;Inexpensive pro audio amplifiers such as this one can provide kilowatts of power. Image © Harman.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Inexpensive pro audio amplifiers such as this one can provide kilowatts of power. Image © Harman.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;There is one major downside to using pro audio amplifiers &amp;ndash; virtually all of them have built-in fans, and some of these fans can get quite noisy. It&amp;rsquo;s challenging to keep an amplifier that provides kilowatts of power cool without a fan. Fortunately, there are models with relatively quiet fans that only start spinning when the amplifier is driven hard. For those amplifiers with loud fans, putting them in another room or &lt;a href=&#34;https://lackofimagination.org/2023/09/three-power-amp-fan-mods/&#34;&gt;replacing the existing fans with quieter ones&lt;/a&gt; are options to consider, but the latter would likely void your warranty.&lt;/p&gt;
&lt;p&gt;Another potential downside is that unlike commercial subwoofers with built-in plate amplifiers, pro audio amplifiers are often stand-alone units. While plate amplifiers do exist for DIY subwoofers, they aren&amp;rsquo;t very cost-effective. On the upside, stand-alone amplifiers are potentially more reliable because they offer better cooling.&lt;/p&gt;
&lt;h5 id=&#34;building-a-box&#34;&gt;Building a box&lt;/h5&gt;
&lt;p&gt;Building a basic sealed subwoofer box isn&amp;rsquo;t difficult. It requires six sides cut from 18 mm (~3/4&amp;quot;) MDF or high-quality plywood (preferably made from void-free hardwoods such as Baltic birch), a cutout for the driver, one or two braces to strengthen the box, some wood glue, and a few clamps to hold everything together until the glue dries. Using silicone caulk to seal all internal joints will ensure the box is airtight.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/diy_subwoofer_build_with_wood_clamps.jpg&#34;  alt=&#34;Some wood glue and a few clamps are enough to make a box.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Some wood glue and a few clamps are enough to make a box.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;While it&amp;rsquo;s not strictly necessary, adding some polyester fiber stuffing inside the box can make the internal volume appear slightly larger to the driver(s), allowing them to play a bit deeper. For large boxes, stuffing also helps eliminate most resonances caused by sound waves bouncing around inside the box.&lt;/p&gt;
&lt;p&gt;Drivers can be mounted using regular wood screws (pre-drilling pilot holes is highly recommended to avoid splitting the wood). Heavy-duty drivers weighing over 10 kg (~22 pounds) may require thicker baffles (mounting surfaces) and thicker screws.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/subwoofer_box_speaker_terminal_cutout.jpg&#34;  alt=&#34;Marking the cutout for a speaker terminal cup.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Marking the cutout for a speaker terminal cup.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Dual opposed subwoofers&lt;/strong&gt;:&lt;br&gt;
Subwoofer boxes housing a single driver tend to &amp;ldquo;walk around&amp;rdquo; when pushed hard. To fully eliminate this behavior, you can use two drivers placed on opposite sides of the box. Any movement made by one driver will be immediately canceled out by the opposing driver. You can even balance a coin on top, no matter how much the drivers are moving! Dual opposed subwoofers also have the added benefit of providing more even bass output across the room, regardless of where the drivers are facing.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/dual_opposed_subwoofer_enclosure_sketch.png&#34;  alt=&#34;Sealed enclosure for dual opposed subwoofers with a center brace.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Sealed enclosure for dual opposed subwoofers with a center brace.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;When using two drivers, you can connect them in series (which doubles the resistance) or in parallel (which halves the resistance). For example, two 4 ohm drivers connected in series would present an 8 ohm load to the amplifier. When connected in parallel, they would present a 2 ohm load. Subwoofer drivers often come with instructions on how to connect multiple drivers together. Having a multimeter is helpful to verify the final resistance. Just keep in mind that some amplifiers may not support low resistances, especially when they are set to bridge mode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Making it look good&lt;/strong&gt;:&lt;br&gt;
Building a box isn&amp;rsquo;t difficult, but making it presentable may take some effort. Of course, there&amp;rsquo;s nothing stopping you from leaving the box as is, which won&amp;rsquo;t affect the performance one bit.&lt;/p&gt;
&lt;p&gt;If you have a router, you can apply a roundover or chamfer to the edges for a nicer look. A router also comes in handy when making perfect circular cuts for the drivers. If you plan to flush mount the drivers, a router may even become a necessity.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/trim_router_to_shape_edges.jpg&#34;  alt=&#34;A trim router comes handy to shape edges and also to make precise circular cuts. Image © Bosch&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;A trim router comes handy to shape edges and also to make precise circular cuts. Image © Bosch&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;There are basically two options for finishing: you can paint the box or apply a thin veneer made of vinyl or wood. Whichever path you choose, the surface needs to be sanded smooth first. This can be done by hand, but if you plan to build more than one box, you might want to invest in or borrow a random orbital sander, which can significantly speed up the process.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/painted_dual_opposed_subwoofer_box.jpg&#34;  alt=&#34;A dual opposed subwoofer box painted white with a paint gun.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;A dual opposed subwoofer box painted white with a paint gun.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;If the box is made of MDF and you&amp;rsquo;ve decided to paint it, the edges must be sealed first &amp;ndash; a mixture of half water and half wood glue can be used for this purpose. Otherwise, the edges will absorb the paint, resulting in an uneven surface finish. You can use a foam brush to apply the paint if you don&amp;rsquo;t mind some brush marks, but spraying is clearly superior. If you don&amp;rsquo;t have a paint gun, spray cans can also be used effectively. After the paint dries, it&amp;rsquo;s a good idea to apply several coats of clear coat to protect the paint, which also add a nice satin or gloss sheen.&lt;/p&gt;
&lt;h5 id=&#34;measuring-and-correcting-output&#34;&gt;Measuring and correcting output&lt;/h5&gt;
&lt;p&gt;The in-room frequency response of subwoofers has nothing to do with their natural outdoor response. Resonances (also called standing waves) occur inside the room, and the resonant frequencies are determined by the room dimensions. The smooth open-air response of a subwoofer is replaced by peaks and valleys (also called nulls). Peaks cause boomy bass, while nulls result in weak bass. To make matters worse, the magnitude of these peaks and nulls varies from one location to another within the room &amp;ndash; any corrections made using the automatic room correction features of an AV receiver or through DIY parametric EQ will only be applicable to the small area where the measurements are taken.&lt;/p&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/in_room_simulated_frequency_response_of_two_subwoofers_vs_open_air.png&#34;  alt=&#34;Simulated frequency response of two subwoofers in-room (solid line) vs open-air (dashed line) in listening position. The colored vertical lines correspond to this particular room&amp;#39;s resonant frequencies.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Simulated frequency response of two subwoofers in-room (solid line) vs open-air (dashed line) in listening position. The colored vertical lines correspond to this particular room&amp;#39;s resonant frequencies.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;A powerful free software application called &lt;a href=&#34;https://www.roomeqwizard.com&#34;&gt;REW&lt;/a&gt; (Room EQ Wizard) is a popular choice to make measurements and to automatically calculate the corrections necessary to reduce the magnitude of peaks. Nulls are better left alone because attempting to boost them can easily overload your subwoofer&amp;rsquo;s amplifier. REW also features a quite accurate Room Sim tool, which can simulate the frequency response of your subwoofers in different parts of your room without the need to physically move the subwoofers or make hundreds of measurements.&lt;/p&gt;
&lt;h4 id=&#34;end-game-double-bass-array&#34;&gt;End Game: Double Bass Array&lt;/h4&gt;
&lt;p&gt;If you have a rectangular room with all parallel surfaces (no sloped ceilings) and access to at least two subwoofers, there is a special configuration known as a double bass array (DBA) that can virtually eliminate all room resonances up to a certain frequency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For two subwoofers:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Center a subwoofer (halfway from the side wall and floor) against the front wall and another subwoofer against the rear wall.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;For four subwoofers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raise the front two subwoofers halfway from the floor, and position them at locations 1/4 of the front wall width from each side. Repeat this setup for the rear wall.&lt;/li&gt;
&lt;/ul&gt;

 
  
  
  
  
    
      
    
  
    
  


&lt;br&gt;
&lt;div class=&#34;figure center&#34; &gt;
  
    &lt;img class=&#34;fig-img&#34; src=&#34;https://lackofimagination.org/images/double_bass_array_for_four_subwoofers.png&#34;  alt=&#34;Elevation view of double bass array subwoofer locations for four subwoofers (two against front wall, two against rear wall). For a 4 meter (~13 feet) wide and 2.4 meter (~8 feet) high room.&#34;&gt;
  
   
    &lt;span class=&#34;caption&#34;&gt;Elevation view of double bass array subwoofer locations for four subwoofers (two against front wall, two against rear wall). For a 4 meter (~13 feet) wide and 2.4 meter (~8 feet) high room.&lt;/span&gt;
  
&lt;/div&gt;

  &lt;div style=&#34;clear:both;&#34;&gt;&lt;/div&gt;

&lt;p&gt;Now, you need to delay the signal going to the rear subwoofers by a certain amount, which can be calculated by simply dividing the length of the room by the speed of sound (about 0.343 meters per millisecond). For example, in a 5-meter-long room, the delay would be 5 / 0.343 = about 14.5 milliseconds. You will also need to reverse the polarity of the rear subwoofers, which can be achieved by toggling the polarity switch on powered subwoofers or by switching the positive and negative wires for passive subwoofers powered by an external amplifier.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what happens: The sound wave generated by the front subwoofers is canceled out by the rear subwoofers at the exact moment it reaches the rear wall, due to the delayed rear signal and reversed polarity. It&amp;rsquo;s as if there is no rear wall, thus eliminating resonances between the front and rear walls. The resonances between the remaining walls aren&amp;rsquo;t excited either, thanks to the strategic placement of the subwoofers.&lt;/p&gt;
&lt;p&gt;The great thing about a DBA is that the bass output across the room will mostly be free of resonances, independent of where you sit. However, you should still avoid sitting too close to the walls.&lt;/p&gt;
&lt;p&gt;Finally, keep in mind that delaying the signal to the rear subwoofers may require the use of an external DSP device or an amplifier with built-in signal delay features. It also helps to have the capability to make measurements to fine-tune the setup. For more information, you can refer to the &lt;a href=&#34;https://en.wikipedia.org/wiki/Double_bass_array&#34;&gt;DBA Wikipedia page&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>All Software have Bugs</title>
      <link>https://lackofimagination.org/2022/10/all-software-have-bugs/</link>
      <pubDate>Thu, 13 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2022/10/all-software-have-bugs/</guid>
      <description>&lt;p&gt;&lt;strong&gt;All Software have Bugs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s inevitable.&lt;br&gt;
You fix some,&lt;br&gt;
New ones appear.&lt;br&gt;
You fix some more,&lt;br&gt;
Old ones come back from the dead&lt;br&gt;
Unless your tests catch them first.&lt;br&gt;
Do you even have time for tests&lt;br&gt;
When the feature backlog is a mile long,&lt;br&gt;
And time to market is everything.&lt;br&gt;
There is this satisfying feeling though&lt;br&gt;
When you have the scalpel in your hand,&lt;br&gt;
Removing features left and right,&lt;br&gt;
Cutting corners no one would notice,&lt;br&gt;
Or so you hope.&lt;br&gt;
But it must be done,&lt;br&gt;
You can always fix things later.&lt;br&gt;
This is software after all &amp;ndash;&lt;br&gt;
Infinitely malleable, infinitely soft.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Favorite Exercise</title>
      <link>https://lackofimagination.org/2022/06/favorite-exercise/</link>
      <pubDate>Thu, 23 Jun 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2022/06/favorite-exercise/</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t like to exercise. I mostly think of it as a distraction, but what bothers me the most is the amount of time spent for exercise could have been dedicated to other, you know, more fun activities.&lt;/p&gt;
&lt;p&gt;We all need to exercise regularly to stay in shape and be healthy. I don&amp;rsquo;t think anyone would disagree with that, but if exercise takes too much time, I know I wouldn&amp;rsquo;t do it. So, I needed a compromise. I needed to find an exercise that would take minimal time, and yet still be as effective as others that would take a long time to complete. Here is my list of criteria for an ideal exercise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Should take minimal time.&lt;/li&gt;
&lt;li&gt;Should work as many muscles as possible at the same time.&lt;/li&gt;
&lt;li&gt;Can be done anywhere with minimal or no equipment (I&amp;rsquo;m certainly not going to a gym and spend even more time on the way to and from there. I also find it strange to pay for doing physical exercise. Not long ago, if you wanted to exercise, you just chopped wood for your fireplace or worked at a construction site and &lt;em&gt;got paid&lt;/em&gt; for it, but I digress.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These criteria can be fortunately met with just one exercise, and that&amp;rsquo;s pull-ups. Yes, pull-ups. I know they are hard. Many people can&amp;rsquo;t even do one pull-up. I couldn&amp;rsquo;t do one when I first started either. But, as soon as you can do one pull-up (or its slightly easier variation: chin-up), the rest should become easier.&lt;/p&gt;
&lt;p&gt;What is great about pull-ups is that since they are so hard to do (think of pulling 100% of your body weight), it often takes less than a minute to complete one set, and they work your arms and almost your entire upper body too. Doing pull-ups is probably the easiest way to achieve the coveted V body shape.&lt;/p&gt;
&lt;p&gt;Now, I must say that pull-ups are significantly harder for women than men. Women tend to have less muscle mass on their upper bodies. But, they can still do pull-ups. My wife, after seeing me doing pull-ups, tried them herself. She can barely do one pull-up, and yet after about a month of casual daily pull-up sessions, her posture is noticeably more erect now. That&amp;rsquo;s because not only do pull-ups build muscle, but they are great for stretching too. You literally stretch your entire body while doing pull-ups. In fact, Arnold Schwarzenegger aka the Terminator used to do pull-ups for warming up before doing his regular routine of body building. We are lucky that we could just stop after doing pull-ups and call it a day while he had to go through hours and hours of exercise every day. Well, he clearly enjoyed doing body building unlike many of us. Each to his own, as they say.&lt;/p&gt;
&lt;p&gt;To improve quickly in pull ups, there is a trick, and it&amp;rsquo;s a simple trick too: don&amp;rsquo;t push yourself to failure. That is, don&amp;rsquo;t just follow the common advice of trying to do as many repetitions as you can. If you do that, you can brag to your friends that you can do, say, 10 pull-ups, but you can do that many only once or maybe twice a day, and you will always feel exhausted after each session. In other words, it will start to feel like a chore, rather than something you look forward to doing.&lt;/p&gt;
&lt;p&gt;I read about that trick in one of Pavel Tsatsouline&amp;rsquo;s books. He is a well known fitness instructor who also happens to write fun to read books about strength training. What he recommends for any kind of exercise, not just pull-ups, is basically this: Do as many repetitions as you can, but stop a few repetitions short of failure. That&amp;rsquo;s all there is to it.&lt;/p&gt;
&lt;p&gt;I stop doing pull-ups at about 3/4 of the maximum repetitions I can do, and I try to do as many sets as possible spread throughout the day. The pull-up bar is attached to the door frame of my wood workshop in the basement, and the room next to it has my home theater. So, whenever I go down there I try to do one set, and then do some woodworking, watch a movie, or just go back upstairs to do something else, and I would still feel fresh. That way, it doesn&amp;rsquo;t feel like I&amp;rsquo;m doing work, I want to do more of it, and my body (or should I say brain) isn&amp;rsquo;t conditioned against its limits prematurely. When my usual 3/4 max repetitions start to feel too easy, I just add another repetition.&lt;/p&gt;
&lt;p&gt;Lately, I decided to work on my abdominal muscles that the all-mighty pull-ups could sadly not reach. The obvious exercise choice would be sit-ups, but they don&amp;rsquo;t quite meet my 3-item criteria, and a back condition I have makes doing them somewhat uncomfortable. So, I decided to give push-ups a try. Not only do they work the abdominal muscles, but also work a few of the muscles on the upper body pull-ups don&amp;rsquo;t quite work. Another reason why I chose push-ups is that combined with pull-ups, they are the staple exercises of the militaries around the world. So, all those drill sergeants certainly know what they are doing. Right?&lt;/p&gt;
&lt;p&gt;Anyway, after getting my upper body in shape, I thought doing push-ups would be a piece of cake, but it didn&amp;rsquo;t quite work that way, at least initially. After my first try, during which I apparently pushed myself too hard, I was sore for two days. So, after that I followed my own advice of stopping well short of the maximum repetitions I could do, and things started to improve from there.&lt;/p&gt;
&lt;p&gt;With push-ups, you lift about 70% of your body weight, and that seemingly small 30% difference allows your body to do about three to four times as many repetitions as compared to pull-ups. In other words, if you can do 5 pull-ups, you should be able to do 15 to 20 push-ups, but let&amp;rsquo;s forget those numbers. I think what really matters isn&amp;rsquo;t the number of repetitions one could do (after all we are talking about doing exercise casually), but keeping the motivation to exercise high. With only a few minutes total to spend on exercise each day, I can certainly manage to keep my motivation high. If you keep doing something every day, even if you are really bad at it at first, it&amp;rsquo;s inevitable that you will improve over time.&lt;/p&gt;
&lt;p&gt;Finally, a word of caution: I&amp;rsquo;m not an expert at strength training, and I&amp;rsquo;m just sharing my informed opinions with you. Although it&amp;rsquo;s unlikely to &lt;em&gt;seriously&lt;/em&gt; injure yourself by doing pull-ups or push-ups, it isn&amp;rsquo;t impossible either. Please use some common sense while you exercise, listen to your body, and if some part of it hurts badly, it&amp;rsquo;s probably a good idea to rest a day or two (or more) while your body heals. If you have some health condition, it&amp;rsquo;s wise to talk to your doctor first before following the advice you read on some random web site.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Lodash, a Middle Ground between JavaScript and TypeScript</title>
      <link>https://lackofimagination.org/2022/06/lodash-a-middle-ground-between-javascript-and-typescript/</link>
      <pubDate>Sun, 19 Jun 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2022/06/lodash-a-middle-ground-between-javascript-and-typescript/</guid>
      <description>&lt;p&gt;I still remember a quote from a computer magazine I read when I was 12. In an article comparing the programming languages of the time, a C developer described the Pascal programming language as &amp;ldquo;having a mother hen hovering over you, watching your every move, making sure you don&amp;rsquo;t screw up.&amp;rdquo; I didn&amp;rsquo;t fully understand it at the time, but he was most likely referring to the strong type checking in Pascal, which could lead to compiler and runtime errors. If you mixed and matched types in a C program, the program would probably continue to run and fail only when something went terribly wrong.&lt;/p&gt;
&lt;p&gt;I think our C developer was mostly right in retrospect. Generally speaking, there isn&amp;rsquo;t an overwhelming need for strict type checking. Coding is hard as it is. Developers spend a significant amount of time fighting with compilers just to get their code run. Dynamically typed programming languages like JavaScript, PHP, and Python are popular for a good reason. It&amp;rsquo;s a lot easier to get your code running in them. Now, please don&amp;rsquo;t get me wrong &amp;ndash; I think strong type checking is important in avoiding quite a few bugs before they occur, but is the price we pay worth the benefits?&lt;/p&gt;
&lt;p&gt;I think there is a happy middle ground between plain JavaScript and its strongly typed variant TypeScript, and that is &lt;a href=&#34;https://lodash.com&#34;&gt;Lodash&lt;/a&gt;, the popular Swiss Army knife JavaScript library. At &lt;a href=&#34;https://faradai.ai&#34;&gt;Faradai&lt;/a&gt; we make heavy use of Lodash. The number one reason why we use it is because Lodash eliminates a whole range of type checks. If you pass an undefined variable to a Lodash function, the function will return gracefully, and the execution will continue from the next line without throwing any errors.&lt;/p&gt;
&lt;p&gt;Lodash has a large number of handy functions, but probably the most important one is the simplest: &lt;code&gt;_.get()&lt;/code&gt;. It allows you to access an arbitrary property of an object no matter how deep it is like so: &lt;code&gt;_.get(table, &#39;data.0.childNodes.3&#39;)&lt;/code&gt; and if the property doesn&amp;rsquo;t exist, no harm is done. This is especially useful in frontend code &amp;ndash; displaying a table cell empty isn&amp;rsquo;t usually the end of the world when the alternative is throwing the infamous &lt;em&gt;&amp;lsquo;undefined&amp;rsquo; is not an object&lt;/em&gt; error, breaking the whole table and maybe some other things too.&lt;/p&gt;
&lt;p&gt;There are times, of course, especially when you are dealing with mission-critical code, you need to make sure that the data you are processing is in the right format. However, in such cases, you need to have a good automated test suite anyway. All the type checking in the world can&amp;rsquo;t save poorly tested code. It&amp;rsquo;s the age old validity vs. reliability dilemma. Your code may be 100% valid, but it can still be incorrect.&lt;/p&gt;
&lt;p&gt;Another reason we prefer to use Lodash is that Lodash/fp, in my opinion, is the most convenient way to do functional programming in JavaScript. Yes, &lt;a href=&#34;https://github.com/lodash/lodash/wiki/FP-Guide&#34;&gt;the documentation for Lodash/fp&lt;/a&gt; is terrible, but all you really need to know are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The first argument to a Lodash function is now passed from &amp;ldquo;outside&amp;rdquo; like &lt;code&gt;_.get(&#39;property&#39;)(myObject)&lt;/code&gt; instead of &lt;code&gt;_.get(myObject, &#39;property&#39;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;_.flow()&lt;/code&gt; liberally to pass one function&amp;rsquo;s output to the next. Lodash handles the necessary &amp;ldquo;auto-currying&amp;rdquo; for you in the background.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It &lt;em&gt;is&lt;/em&gt; possible to do a great deal of functional programming in a mixed-paradigm language like JavaScript without knowing a thing about function composition, currying, or point-free style. I think there are quite a few developers out there who would appreciate the elegance of the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lodash/fp&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;users&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Katniss&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Everdeen&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Peeta&amp;#39;&lt;/span&gt;,   &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Mellark&amp;#39;&lt;/span&gt;,  &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{&lt;span style=&#34;color:#a6e22e&#34;&gt;firstName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Seneca&amp;#39;&lt;/span&gt;,  &lt;span style=&#34;color:#a6e22e&#34;&gt;lastName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Crane&amp;#39;&lt;/span&gt;,    &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;disabled&amp;#39;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getFullNamesOfActiveUsers&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;users&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;flow&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;status&amp;#39;&lt;/span&gt;)(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;firstName&amp;#39;&lt;/span&gt;)(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;lastName&amp;#39;&lt;/span&gt;)(&lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	)(&lt;span style=&#34;color:#a6e22e&#34;&gt;users&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;getFullNamesOfActiveUsers&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;users&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The code is read naturally from left to right. The lodash functions simply transform the passed data without changing it, and finally and perhaps most importantly the code is easy to follow.&lt;/p&gt;
&lt;p&gt;I think Lodash/fp has the potential to transform JavaScript (and Node.js) into a dynamically typed functional programming language with the least amount of effort. If you wish to add some strong type checking on top, that&amp;rsquo;s fine too.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Worth a Read:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://simonsmith.io/dipping-a-toe-into-functional-js-with-lodash-fp&#34;&gt;Dipping a toe into functional JS with lodash/fp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Writing from a Software Developer&#39;s Perspective</title>
      <link>https://lackofimagination.org/2022/04/writing-from-a-software-developers-perspective/</link>
      <pubDate>Sat, 23 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2022/04/writing-from-a-software-developers-perspective/</guid>
      <description>&lt;p&gt;I write because I want to share what I know with others. Yet the software developer in me makes writing hard for me. See, in coding the internal structure needs to be consistent. The code we write is like a house of cards. If we make even one tiny mistake, the whole thing may come falling down. So, when I write, I try to be consistent, which is not a bad thing in itself of course, but the desire for consistency makes writing a lot more difficult for me than it should be.&lt;/p&gt;
&lt;p&gt;Haruki Murakami once said you should not be too intelligent to write fiction. Well, I only write non-fiction, but strictly speaking, nothing we write is entirely non-fiction. Even in scientific writing &amp;ndash;especially the popular science kind&amp;ndash; there can be quite a lot that is assumed to be true. The software developer in me gets pedantic at times, and tries really hard to ground every aspect of an article in reality. Most of the time, this attitude means I have to put a lot of extra work into making sure everything is accurate and makes sense, and yet the end result may turn out to be quite boring.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve always wanted to be able to write the way I speak. Apparently there are authors who can write like that, seemingly never lifting a finger from the keyboard so to speak, but there are those like Stephen King who can&amp;rsquo;t. Stephen King was brave enough to share excerpts from some of his early drafts, and they are pretty bad. But, what makes the end result infinitely better is the intense polishing phase. When I try to write and polish at the same time, which I often fall into the habit of doing, I rarely get anywhere.&lt;/p&gt;
&lt;p&gt;The best way to write, at least for me, is to write as quickly as possible as Stephen King suggested, and polish later. Polishing doesn&amp;rsquo;t just mean editing the text though. It involves adding new viewpoints or removing passages that slows down pacing &amp;ndash; especially the pedantic parts I&amp;rsquo;m so prone to include.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Our Experience with PostgreSQL on ZFS</title>
      <link>https://lackofimagination.org/2022/04/our-experience-with-postgresql-on-zfs/</link>
      <pubDate>Sat, 16 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2022/04/our-experience-with-postgresql-on-zfs/</guid>
      <description>&lt;p&gt;First a little context: At &lt;a href=&#34;https://faradai.ai&#34;&gt;Faradai&lt;/a&gt; we had been using PostgreSQL on the &lt;a href=&#34;https://en.wikipedia.org/wiki/Ext4&#34;&gt;ext4 file system&lt;/a&gt; for a number of years until it recently became evident that the constantly growing database size (over 5 TB at the time) would start to cause issues in general performance, cloud costs, and the ability to have reliable backups. We reviewed a number of alternative file systems, and chose ZFS mainly for its following three features:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Instantenous Filesystem Snapshots&lt;/strong&gt;&lt;br&gt;
This makes creating incremental database backups a piece of cake, something we didn&amp;rsquo;t have before. It never fails to amaze me how fast and easy it is to revert to a snapshot. This is doubly important if one is managing a write-heavy database that changes every second. We use the snapshot feature to transfer hourly snapshots to a backup server which also doubles as our development database server. More on that later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Built-in Compression&lt;/strong&gt;&lt;br&gt;
Most of the data we collect are time-series sensor measurements, and they compress really well. In fact, the compression ratio is over 4.5x in our case! That naturally decreases our disk storage needs by a similar ratio. Another side benefit is that the disk throughput would be higher because data takes less space on disk and would require fewer I/O operations and less bandwidth. CPU usage is increased slightly when compression is enabled, which is fine with us.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Built-in Support for Secondary Caching on SSD Disks&lt;/strong&gt;&lt;br&gt;
Block storage on the cloud is great until it isn&amp;rsquo;t. Yes, we are getting virtually infinite storage that can be dynamically allocated and resized, and it tends to be more reliable than individual physical disks, but the network interface to storage is significantly slower. That is the price we pay for flexibility. So, we needed an easy way to utilize the fast physical disks attached to VMs for caching. We don&amp;rsquo;t care if they fail. We want to use them as a secondary cache, and ZFS makes that really easy.&lt;/p&gt;
&lt;p&gt;ZFS has other neat features such as the ability to detect &lt;a href=&#34;https://en.wikipedia.org/wiki/Data_degradation&#34;&gt;bit rot&lt;/a&gt; and its own implementation of software RAID, which aren&amp;rsquo;t really critical for us, but they are still &lt;em&gt;very&lt;/em&gt; nice to have.&lt;/p&gt;
&lt;h3 id=&#34;how-we-do-backups-on-zfs&#34;&gt;How we Do Backups on ZFS&lt;/h3&gt;
&lt;p&gt;There are a number of open-source tools that are used to automate ZFS backups, but our needs aren&amp;rsquo;t complex, and we prefer our critical tooling like backup scripts as simple as possible to avoid unpleasant surprises when things go south. So, we went ahead and wrote a short script and added 2 cron entries. &lt;a href=&#34;#backup-script&#34;&gt;Less than 20 lines in total including comments&lt;/a&gt;. It can&amp;rsquo;t probably get simpler than that thanks to ZFS&amp;rsquo; amazing incremental snapshot feature that finishes its job in seconds. We take 23 hourly and 14 daily snapshots on the production server, and those snapshots are transferred to the backup server. Our backup server initiates and controls the entire process, and it is essentially a perfect replica of our production server, but is of course up to an hour behind.&lt;/p&gt;
&lt;h3 id=&#34;we-live-with-our-backups&#34;&gt;We Live with our Backups&lt;/h3&gt;
&lt;p&gt;Test your backups! How many times have we heard that? Too many to count, but it&amp;rsquo;s absolutely true. Probably one of the most famous examples of the &lt;a href=&#34;https://thenextweb.com/news/how-pixars-toy-story-2-was-deleted-twice-once-by-technology-and-again-for-its-own-good&#34;&gt;untested backups went bad is Toy Story 2&lt;/a&gt;. The entire movie production could have been lost due to corrupt backups and the infamous &amp;lsquo;rm -rf&amp;rsquo; command in the wrong directory. Fortunately, they discovered a few weeks old copy of the film assets on the computer of a pregnant employee working from home, and saved the production. It&amp;rsquo;s not hard to imagine that others in a similar situation might not be so lucky.&lt;/p&gt;
&lt;p&gt;Probably one of the most effective ways to test the backups is to actually use their contents, and use them every day. Here is how we do it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every few weeks, we pick a recent daily snaphot on the backup server, and create a ZFS clone from the snapshot to make it writable.&lt;/li&gt;
&lt;li&gt;We then make the PostgreSQL instance running on the backup server use the clone&amp;rsquo;s mount point as its data directory.&lt;/li&gt;
&lt;li&gt;Finally, we restart PostgreSQL to have it recognize the new data directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The backup server is now doubling as our development database server, and the process of taking hourly snapshots from the production server continues to run in the background.&lt;/p&gt;
&lt;h3 id=&#34;technical-bits&#34;&gt;Technical Bits&lt;/h3&gt;
&lt;h4 id=&#34;installation&#34;&gt;Installation&lt;/h4&gt;
&lt;p&gt;It takes only a few commands to install ZFS and PostgreSQL on a Linux machine (Ubuntu in our case).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install zfsutils-linux                                          &lt;span style=&#34;color:#75715e&#34;&gt;# Install zfs filesystem packages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo fdisk -l                                                            &lt;span style=&#34;color:#75715e&#34;&gt;# Take note of disk names for the next step&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zpool create -o autoexpand&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;on db /dev/nvme1n1 /dev/nvme2n1          &lt;span style=&#34;color:#75715e&#34;&gt;# Create a striped RAID0 pool from 2 block storage volumes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zpool add db cache /dev/nvme3n1                                     &lt;span style=&#34;color:#75715e&#34;&gt;# Add local SSD drive as cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zfs create db/data -o mountpoint&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/lib/postgresql                &lt;span style=&#34;color:#75715e&#34;&gt;# Create dataset and mount it to use for PostgreSQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zfs set compression&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;lz4 db                                          &lt;span style=&#34;color:#75715e&#34;&gt;# Enable zfs compression&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zfs set atime&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;off db                                                &lt;span style=&#34;color:#75715e&#34;&gt;# Disable atime for improved performance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zfs set recordsize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;32k db                                           &lt;span style=&#34;color:#75715e&#34;&gt;# Set zfs record size to 32 KB (default: 128 KB)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo reboot                                                              &lt;span style=&#34;color:#75715e&#34;&gt;# Reboot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zpool import db                                                     &lt;span style=&#34;color:#75715e&#34;&gt;# Import zfs pool; has to be done only once after first reboot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget --quiet -O - &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -  &lt;span style=&#34;color:#75715e&#34;&gt;# Add PostgreSQL repository key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo sh -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;echo &amp;#34;deb http://apt.postgresql.org/pub/repos/apt \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$(lsb_release -cs)-pgdg main&amp;#34; &amp;gt; /etc/apt/sources.list.d/pgdg.list&amp;#39;&lt;/span&gt;       &lt;span style=&#34;color:#75715e&#34;&gt;# Add PostgreSQL repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt update                                                          &lt;span style=&#34;color:#75715e&#34;&gt;# Update apt packages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install postgresql-14 postgresql-client-14                      &lt;span style=&#34;color:#75715e&#34;&gt;# Install PostgreSQL 14&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo sysctl -w vm.swappiness&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;                                           &lt;span style=&#34;color:#75715e&#34;&gt;# Tell Linux not to use swap unless absolutely necessary&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;vm.swappiness=1&amp;#39;&lt;/span&gt; | sudo tee -a /etc/sysctl.conf                    &lt;span style=&#34;color:#75715e&#34;&gt;# Append &amp;#39;vm.swappiness=1&amp;#39; to systctl.conf for permanent effect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Careful readers will notice that we have created a striped RAID0 pool out of two disks on a production server, which should be verboten, and I would normally agree with you if these were actual physical disks, but they aren&amp;rsquo;t. The disks are commissioned from network attached storage, and they are managed by a tier-1 cloud provider. We think they know what they are doing, and they have the necessary redundancy in place. In any case, we have a backup/development server that is ready to switch to production in a moment&amp;rsquo;s notice and a separate set of backups stored on object storage.&lt;/p&gt;
&lt;p&gt;After the installation, the settings in postgresql.conf needs to be optimized to make better use of the number of CPUs and amount of RAM on the machine. &lt;a href=&#34;https://pgtune.leopard.in.ua&#34;&gt;PGTune&amp;rsquo;s recommendations&lt;/a&gt; is generally a good starting point.&lt;/p&gt;
&lt;p&gt;Finally, we disable full page writes in PostgreSQL, and restart the service to eliminate unnecessary disk writes. By design it is impossible to write partial pages on ZFS.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ALTER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;SYSTEM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; full_page_writes&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;off&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CHECKPOINT&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;h4 id=&#34;backup-script&#34;&gt;Backup Script&lt;/h4&gt;
&lt;p&gt;Here is our backup script: &amp;ldquo;remote_db_snapshot&amp;rdquo; running on our backup server. The script accesses the production server (remote) over SSH, switches PostgreSQL to online backup mode, takes a snapshot, exits backup mode, prunes old snapshots, and finally copies the new snapshot over.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HOST&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;some-user@prod-db&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NOW&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;date --iso-8601&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;minutes | sed &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/:/-/g&amp;#39;&lt;/span&gt; | cut -c 1-16&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DELETE_OLDEST&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;date -d &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$1&lt;span style=&#34;color:#e6db74&#34;&gt; ago&amp;#34;&lt;/span&gt; --iso-8601&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;minutes | sed &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/:/-/g&amp;#39;&lt;/span&gt; | cut -c 1-16&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LAST_SNAPSHOT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;sudo zfs list -t snapshot | tail -n1 | cut -d &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; -f 1&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Switch remote PostgreSQL to backup mode, take a ZFS snapshot, and exit from PostgreSQL backup mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh $HOST &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sudo -u postgres psql -c \&amp;#34;SELECT pg_start_backup(&amp;#39;&lt;/span&gt;$NOW&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;, true);\&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh $HOST &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sudo zfs snapshot db/data@&lt;/span&gt;$NOW&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh $HOST &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sudo -u postgres psql -c &amp;#39;SELECT pg_stop_backup();&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Destroy oldest snapshot on local server, and if successful delete on remote&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo zfs destroy -v db/data@$DELETE_OLDEST &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ssh $HOST &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sudo zfs destroy -v db/data@&lt;/span&gt;$DELETE_OLDEST&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Copy newly taken snapshot from remote server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh $HOST &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sudo zfs send -cRi &lt;/span&gt;$LAST_SNAPSHOT&lt;span style=&#34;color:#e6db74&#34;&gt; db/data@&lt;/span&gt;$NOW&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; | sudo zfs receive -vF db/data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The cron entries:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-cron&#34; data-lang=&#34;cron&#34;&gt;0 1-23 * * *	. $HOME/.profile; ~/scripts/remote_db_snapshot &amp;#39;1 day&amp;#39;   2&amp;gt;&amp;amp;1 | logger -t remote_db_snapshot_hourly
0    0 * * *	. $HOME/.profile; ~/scripts/remote_db_snapshot &amp;#39;2 weeks&amp;#39; 2&amp;gt;&amp;amp;1 | logger -t remote_db_snapshot_daily
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To make it easy to keep track, our snapshots are named with the time they are taken:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Daily snapshots&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;...
db/data@2022-03-30T00-00
db/data@2022-03-31T00-00
db/data@2022-04-01T00-00
db/data@2022-04-02T00-00
db/data@2022-04-03T00-00
db/data@2022-04-04T00-00
db/data@2022-04-05T00-00
db/data@2022-04-06T00-00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;24-hour rolling window snapshots:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;db/data@2022-04-06T18-00
db/data@2022-04-06T19-00
db/data@2022-04-06T20-00
db/data@2022-04-06T21-00
db/data@2022-04-06T22-00
db/data@2022-04-06T23-00
db/data@2022-04-07T00-00
...
db/data@2022-04-07T11-00
db/data@2022-04-07T12-00
db/data@2022-04-07T13-00
db/data@2022-04-07T14-00
db/data@2022-04-07T15-00
db/data@2022-04-07T16-00
db/data@2022-04-07T17-00
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;conclusion-and-disclaimer&#34;&gt;Conclusion and Disclaimer&lt;/h3&gt;
&lt;p&gt;Our experience with PostgreSQL on ZFS has been absolutely positive so far, but we certainly don&amp;rsquo;t know everything there is to know about the duo. We are confident that we have a handle on the important bits though. So, please do take our experience with a grain of salt, and if you think we are doing something &lt;em&gt;terribly&lt;/em&gt; wrong or have any recommendations, please let me know, and I will update this article.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Worth a Read:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://people.freebsd.org/~seanc/postgresql/scale15x-2017-postgresql_zfs_best_practices.pdf&#34;&gt;PostgreSQL + ZFS: Best Practices and Standard Procedures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arstechnica.com/information-technology/2020/05/zfs-101-understanding-zfs-storage-and-performance/&#34;&gt;ZFS 101—Understanding ZFS storage and performance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Shared by:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://postgresweekly.com/issues/451&#34;&gt;Postgres Weekly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.scalingpostgres.com/episodes/212-slow-queries-4tb-upgrade-postgres-on-zfs-storage-stampede/&#34;&gt;Scaling Postgres&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/PeterZaitsev/status/1516760596724690944&#34;&gt;Peter Zaitsev, Founder &amp;amp; CEO at Percona&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Choose Your Cloud Dependencies Wisely</title>
      <link>https://lackofimagination.org/2022/04/choose-your-cloud-dependencies-wisely/</link>
      <pubDate>Fri, 15 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2022/04/choose-your-cloud-dependencies-wisely/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Vendor lock-in:&lt;/strong&gt; An unfortunate condition that forces a person or company to continue using a disliked product or service just because they invested in it so much. Vendor lock-in used to affect mostly large companies using mission critical legacy applications, but as more and more companies move to the cloud, cloud providers started to rediscover the joys of vendor lock-in by offering &amp;ldquo;value-added&amp;rdquo; services that simplify the lives of software developers who have better things in life than managing servers and databases.&lt;/p&gt;
&lt;p&gt;I think most anyone would agree that virtual machines (VMs) that one could launch and destroy in seconds, and easily resizable block storage attached to such machines are generally good things. We don&amp;rsquo;t have to buy or rent servers which have more capacity than we need. We also don&amp;rsquo;t do really low-level machine maintenance tasks like replacing failed hard drives anymore. These days we are not really managing servers; they are already managed for us, but the thing is, given so much competition, there isn&amp;rsquo;t probably much profit left to cloud providers for offering plain-vanilla VMs. Where is the value-add in that, they would ask.&lt;/p&gt;
&lt;p&gt;Put yourself in the shoes of a cloud provider eager to increase their stock price, and start pondering questions like &amp;ldquo;what are the things that are very hard to change in software development once you are committed to them?&amp;rdquo; Easy, the programming language itself, certain libraries such as ORMs, …and the databases used. Ah, the epiphany!&lt;/p&gt;
&lt;p&gt;Cloud providers can&amp;rsquo;t really create their own languages or libraries, but they can all offer their own versions of popular databases in managed form. Managing and scaling databases is scary, they say. &amp;ldquo;You don&amp;rsquo;t have to worry about all that anymore. Just use us, and focus on your code.&amp;rdquo; Sounds tempting, doesn&amp;rsquo;t it? In reality, a managed database offering that is hard to migrate from probably isn&amp;rsquo;t such a good idea. When in doubt, using a PostgreSQL database on a simple VM can get you a long way, and doesn&amp;rsquo;t really require much maintenance, if at all. Sure, you need to monitor the resource usage of the VM from time to time, but all cloud providers offer built-in monitoring services, and it&amp;rsquo;s impossible to get a surprise bill for a simple VM as opposed to managed database servers that can auto scale. The worst you could do with a VM is to overload it. If that happens, it&amp;rsquo;s probably because your code is inefficient or you forgot to add an index to a column, or maybe everything is fine, but your app is getting popular. If you really are getting popular, that&amp;rsquo;s a good problem to have: upgrade the VM with more cores and memory, and go back to coding or whatever you were doing.&lt;/p&gt;
&lt;p&gt;Come to think of it, although I said cloud providers couldn&amp;rsquo;t really create their own languages, they really came close by offering &amp;ldquo;serverless&amp;rdquo; code running environments. Again, it could be even harder to migrate from those offerings than migrating from managed databases, and surprise bills, once again, start to become a very real possibility. Again, when in doubt, running your code on a simple VM deployed by &amp;ldquo;git pull&amp;rdquo; can get you a long, long way.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>About Lack of Imagination</title>
      <link>https://lackofimagination.org/2022/04/about-lack-of-imagination/</link>
      <pubDate>Thu, 14 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lackofimagination.org/2022/04/about-lack-of-imagination/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Imagination is more important than knowledge. For knowledge is limited to all we now know and understand, while imagination embraces the entire world, and all there ever will be to know and understand.&amp;rdquo; - Albert Einstein&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is Aycan Gulez, your host (&lt;a href=&#34;https://www.linkedin.com/in/aycangulez/&#34;&gt;LinkedIn&lt;/a&gt; | &lt;a href=&#34;https://github.com/aycangulez&#34;&gt;GitHub&lt;/a&gt; | &lt;a href=&#34;https://www.youtube.com/@diyspeakerworkshop&#34;&gt;YouTube&lt;/a&gt; | &lt;a href=&#34;https://mastodon.social/@aycangulez&#34;&gt;Mastodon&lt;/a&gt;). I&amp;rsquo;m the Izmir Lab Director at &lt;a href=&#34;https://efsora.com&#34;&gt;Efsora&lt;/a&gt;, a software and AI development company that partners with innovative enterprises and scaleups to deliver large, cutting-edge R&amp;amp;D projects. Previously, I served as CTO at &lt;a href=&#34;https://faradai.ai&#34;&gt;Faradai&lt;/a&gt;, leading the development of Faradai&amp;rsquo;s carbon accounting and energy management software platforms, which are utilized by a number of mid- to large-sized companies to monitor and reduce their energy consumption.&lt;/p&gt;
&lt;p&gt;Why the name &amp;ldquo;Lack of Imagination&amp;rdquo; you might ask. I&amp;rsquo;ve noticed that the more I think I know about a subject, the more confident I get, and the less likely I become to entertain alternative views. This, in my opinion, leads to a dangerous state of mind: a lack of imagination. So, I chose to name this site “Lack of Imagination” to remember that the world is big and there are so many things I don&amp;rsquo;t know, no matter how much I think I know.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m writing this blog to share my views about subjects on which I consider myself at least somewhat qualified to comment. Hopefully, some of the entries on this site will attract the attention of people who can contribute their own viewpoints.&lt;/p&gt;
&lt;p&gt;I would like to thank Alper Gayretoglu, Bilal Ceylan, Edward Hubbard, Emre Yildiz, and Tugce Aker for their feedback and also the people behind the following tools and services powering this web site:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:right&#34;&gt;&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Static site generator:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; with &lt;a href=&#34;https://github.com/kakawait/hugo-tranquilpeak-theme&#34;&gt;Tranquilpeak theme&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Comments:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://coralproject.net&#34;&gt;Coral&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Mailing list:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://listmonk.app&#34;&gt;listmonk&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Web server:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://nginx.org&#34;&gt;nginx&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Web stats:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://goaccess.io&#34;&gt;GoAccess&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Containers:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://www.docker.com&#34;&gt;Docker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Operating system:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://ubuntu.com&#34;&gt;Ubuntu Linux&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;DDOS protection, CDN, SSL:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://www.cloudflare.com&#34;&gt;CloudFlare&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;Hosting:&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;&lt;a href=&#34;https://www.digitalocean.com&#34;&gt;DigitalOcean&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</description>
    </item>
    
  </channel>
</rss>
