<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Let's Make Software]]></title><description><![CDATA[Software engineering tips, guides, and food for thought]]></description><link>https://katbusch.com</link><image><url>https://katbusch.com/img/substack.png</url><title>Let&apos;s Make Software</title><link>https://katbusch.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 30 Apr 2026 13:22:55 GMT</lastBuildDate><atom:link href="https://katbusch.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Kat Busch]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[katbusch@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[katbusch@substack.com]]></itunes:email><itunes:name><![CDATA[Kat Busch]]></itunes:name></itunes:owner><itunes:author><![CDATA[Kat Busch]]></itunes:author><googleplay:owner><![CDATA[katbusch@substack.com]]></googleplay:owner><googleplay:email><![CDATA[katbusch@substack.com]]></googleplay:email><googleplay:author><![CDATA[Kat Busch]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Fun and surprises with closures, references, and inner functions]]></title><description><![CDATA[Learn about closures while exploring the funky behavior of variables captured in closures in 3 common languages]]></description><link>https://katbusch.com/p/fun-and-surprises-with-closures-references</link><guid isPermaLink="false">https://katbusch.com/p/fun-and-surprises-with-closures-references</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Sun, 28 Jan 2024 20:58:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What happens if you try to reassign a hashmap<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> in an inner function, then modify the hashmap with that function&#8217;s return value? It might surprise you to know that it depends on the language.&nbsp;Turns out you&#8217;ll get a different result in JavaScript, Ruby, and Python!</p><p>That means given these 3 virtually identical code blocks below, 3 different things are going on:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VBlp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VBlp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 424w, https://substackcdn.com/image/fetch/$s_!VBlp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 848w, https://substackcdn.com/image/fetch/$s_!VBlp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 1272w, https://substackcdn.com/image/fetch/$s_!VBlp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VBlp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png" width="1456" height="368" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:368,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:192267,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VBlp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 424w, https://substackcdn.com/image/fetch/$s_!VBlp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 848w, https://substackcdn.com/image/fetch/$s_!VBlp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 1272w, https://substackcdn.com/image/fetch/$s_!VBlp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b1db2bd-6289-4353-8be1-3bc11f3d15a3_1986x502.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Reassigning a hashmap in an inner function in JavaScript, Ruby, and Python</figcaption></figure></div><p>Above we have the same code in each language. In English here&#8217;s what it does:</p><ol><li><p>Define a hashmap in a function named <code>outer</code></p></li><li><p>Define an <code>inner</code> function that will set the empty hashmap to a new hashmap with <code>my_key</code> as 9. Then <code>inner</code> returns 7</p></li><li><p>Call <code>inner</code> and set <code>my_key</code> to the return value <code>inner</code></p></li><li><p>Print <code>my_key</code></p></li></ol><p>Feel free to take a second took make sure you understand what&#8217;s going on in the code in the language you&#8217;re most comfortable with. Then let&#8217;s dive in to find out why this sequence gives us 3 different results in 3 different languages. We&#8217;ll review all three closely to see what each prints and why.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://katbusch.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Let's Make Software! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>JavaScript</h2><p>Let&#8217;s get started with JavaScript. Here&#8217;s the same code we saw above.</p><pre><code>function outer() {
  let my_obj = {};
  inner = () =&gt; {
    my_obj = {my_key: 9};
    return 7;
  }
  my_obj['my_key'] = inner();
  console.log(my_obj.my_key);
}
outer()</code></pre><p>So what does JavaScript do? Drumroll please&#8230; it prints 9.</p><p>Wait what? Why does it print <code>my_obj['my_key']</code> as <strong>9</strong> when the last thing we clearly did was assign <strong>7</strong> to <code>my_obj['my_key']</code>?  Feel free to copy and paste the above code into the JavaScript console on this very page to test it out if you don&#8217;t believe me!</p><p>It turns out that we assigned 7 to an <em>old</em> version of <code>my_obj</code>, then we reassigned <code>my_obj</code> in <code>inner</code> (to a totally new object) to have 9, and the new version with 9 is what we&#8217;re printing out. The old version that gets 7 is no longer referenced.</p><p>We can see this more clearly if we save that old version and then print it. You can see the old version prints<strong> 7</strong>, but the new version has <strong>9</strong>.</p><pre><code>function outer() {
  let my_obj = {};
  let my_old_obj = my_obj;
  function inner() {
    console.log(my_obj); <strong>// prints {}</strong>
    my_obj = {my_key: 9};
    return 7;
  }
  my_obj['my_key'] = inner();
  console.log(my_old_obj['my_key']); <strong>// prints 7</strong>
  console.log(my_obj['my_key']); <strong>// prints 9</strong>
}
outer()</code></pre><p>Here&#8217;s what&#8217;s going on: a <em>closure</em> is capturing <code>my_obj</code>. A <em>closure</em> is a function that you can save and pass around and captures references to the variables in the environment at the time the function is defined. That means that inside <code>inner</code>, we have access to <code>my_obj</code> from <code>outer</code>. When we reassign <code>my_obj</code> inside of <code>inner</code>, we&#8217;re reassigning the actual <code>my_obj</code> we created in <code>outer</code>.</p><p>The tricky part is that JavaScript is using a reference to the original <code>my_obj</code> to write <code>inner</code>&#8217;s return value into. So we execute <code>inner</code> which changes <code>my_obj</code>, but we&#8217;re updating <code>my_old_obj</code> instead.</p><p>So to summarize, <strong>JavaScript reassigns your object, but it retains a reference to the original object</strong>.<strong> Then the original object disappears, leaving you with only the one you created in </strong><code>inner</code><strong>.</strong></p><h2>Ruby</h2><p>Now let&#8217;s take a look at Ruby:</p><pre><code>def outer
  my_hash = {}
  def inner
    my_hash = {:my_key =&gt; 9}
    7
  end
  my_hash[:my_key] = inner
  puts my_hash[:my_key] <strong># prints 7</strong>
end
outer</code></pre><p>It looks just like the JavaScript code, but turns out this prints <strong>7</strong>!</p><p>What&#8217;s going on here? We have an inner function, just like in JavaScript, but we don&#8217;t have a <em>closure</em>. That means we&#8217;re not capturing variables from the environment. It turns out <code>my_hash</code> is a totally new local variable in <code>inner</code>. You can especially see that if you try to print out <code>my_hash</code> in <code>inner</code>:</p><pre><code>def outer
  my_hash = {}
  def inner
    puts my_hash #<strong> errors out!</strong>
    7
  end
  my_hash[:my_key] = inner
  puts my_hash[:my_key]
end
outer</code></pre><p>This actually errors out with <em>undefined local variable or method `my_hash' for main:Object</em>.</p><p>The variable doesn&#8217;t exist because it hasn&#8217;t been created in <code>inner</code> and we don&#8217;t have access to anything in <code>outer</code>.</p><p>We can actually create a closure in Ruby, but not like this. We have to use a lambda. &nbsp;This code below creates a closure and actually prints 9, just like our JavaScript code:</p><pre><code>def outer
  my_hash = {}
  inner = lambda do
    my_hash = {:my_key =&gt; 9} # <strong>overwrites my_hash</strong>
    7
  end
  my_hash[:my_key] = inner.call # <strong>overwrites my_hash[:my_key] with 7</strong>
  puts my_hash[:my_key] # <strong>prints 9</strong>
end
outer</code></pre><h2>Python</h2><p>Finally let&#8217;s take a look at Python. Python prints 7.</p><pre><code>def outer():
  my_dict = {}
  def inner():
    my_dict = {'my_key':  9}
    return 7
  my_dict['my_key'] = inner()
  print(my_dict['my_key'])
outer()</code></pre><p>So it seems like we&#8217;ve got the same situation as we do in Ruby. Probably we don&#8217;t have a closure, so if we try to print, we&#8217;ll get an error, right?</p><p>Wrong! In Python we can print out the variable:</p><pre><code>def outer():
  my_dict = {}
  
  def inner():
    print(my_dict) # <strong>prints {}, doesn't error!</strong>
    return 7
  
  my_dict['my_key'] = inner()
  
  print(my_dict['my_key'])
outer()</code></pre><p>So what&#8217;s going on? Is it a closure or not? Turns out in Python it&#8217;s a closure that lets you <em>read</em>, but not <em>write</em> the captured variable. If we try read <em>and</em> write, we get an error:</p><pre><code>def outer():
  my_dict = {}
  
  def inner():
    print(my_dict) # <strong>errors out!</strong>
    my_dict = {'my_key':  9}
    return 7
  
  my_dict['my_key'] = inner()
  
  print(my_dict['my_key'])
outer()</code></pre><p>So we can <em>read</em> the outer variable, we can <em>write</em> a new variable, but we can&#8217;t do both at once!</p><p>There is a workaround though in the form of the Python <code>nonlocal</code> keyword. If we explicitly say that the variable is not supposed to be local and instead it&#8217;s supposed to reference the outer <code>my_dict</code> using this <code>nonlocal </code>keyword, then we&#8217;ll be actually able to both print and read <code>my_dict</code>.</p><pre><code><code>def outer():
  my_dict = {}
  
  def inner():
    nonlocal my_dict
    print(my_dict) # works this time!
    my_dict = {'my_key':  9}
    return 7
  
  my_dict['my_key'] = inner()
  
  print(my_dict['my_key'])
outer() # prints 7</code></code></pre><h2>References, closures, and inner functions</h2><p>Don&#8217;t expect reference reassignment in closures to behave consistently from language to language. It&#8217;s best to be careful when relying on closures since they can often have unintuitive behavior in edge cases. You don&#8217;t want to <a href="https://katbusch.com/p/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f">confuse other developers (or yourself)</a> in your codebase!</p><p>Which behavior do you think is best? If you were designing a programming language, which would you choose?</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://katbusch.com/p/fun-and-surprises-with-closures-references?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thank you for reading Let's Make Software. This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://katbusch.com/p/fun-and-surprises-with-closures-references?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://katbusch.com/p/fun-and-surprises-with-closures-references?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>object in JavaScript, hash in Ruby, dictionary in Python</p></div></div>]]></content:encoded></item><item><title><![CDATA[Looking Under the Hood: The Basics of Relational Databases]]></title><description><![CDATA[An introduction to how relational databases are implemented]]></description><link>https://katbusch.com/p/the-basics-of-relational-databases</link><guid isPermaLink="false">https://katbusch.com/p/the-basics-of-relational-databases</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Mon, 16 Oct 2023 15:24:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f9794447-0eff-4f0a-a6d6-b105cdfa0d56_964x964.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you use databases often and want to understand how they work, this article is for you. It isn&#8217;t a guide on how to use databases or how to design schemas; it&#8217;s just about how standard relational databases are implemented. For a relational databases like MySQL or PostgreSQL, how do things actually work under the hood? How is data laid out on disk? What is this &#8220;Query Planner&#8221; I&#8217;ve heard so much about? Understanding the nuts and bolts of relational databases is a great foundation for optimizing performance and troubleshooting issues.</p><h1>How are rows stored on a disk?</h1><p>Relational databases are stored by <em>row, </em>or as they&#8217;re called in relationship database-land, <em>tuples</em> or <em>records. </em>This means all the data in a particular <em>row</em> of a database is next to each other on disk. This is different from some types of databases (such as those designed for analytics like Snowflake) that store all the data in a given column together.</p><p>Say we have a table called Students that looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6lnM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6lnM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 424w, https://substackcdn.com/image/fetch/$s_!6lnM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 848w, https://substackcdn.com/image/fetch/$s_!6lnM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 1272w, https://substackcdn.com/image/fetch/$s_!6lnM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6lnM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png" width="768" height="344" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:344,&quot;width&quot;:768,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17877,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6lnM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 424w, https://substackcdn.com/image/fetch/$s_!6lnM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 848w, https://substackcdn.com/image/fetch/$s_!6lnM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 1272w, https://substackcdn.com/image/fetch/$s_!6lnM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1416359d-66b2-4837-b5fc-faf1edef4f1a_768x344.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Remember, a disk is just a record of data, so on disk the data is stored like this:</p><pre><code>1 Harry Potter &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Holly &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3 &nbsp; 2 Hermione Granger &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Vine &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 3</code></pre><p>Or another way to look at that same data on disk&#8230;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E2Qc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E2Qc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 424w, https://substackcdn.com/image/fetch/$s_!E2Qc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 848w, https://substackcdn.com/image/fetch/$s_!E2Qc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 1272w, https://substackcdn.com/image/fetch/$s_!E2Qc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E2Qc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png" width="997" height="850" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:850,&quot;width&quot;:997,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:53744,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!E2Qc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 424w, https://substackcdn.com/image/fetch/$s_!E2Qc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 848w, https://substackcdn.com/image/fetch/$s_!E2Qc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 1272w, https://substackcdn.com/image/fetch/$s_!E2Qc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1fb96d8-4819-47a8-9831-0350f40c6aaf_997x850.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice the gaps of just <code>\0</code> (null, empty) repeated? &nbsp;There are a few reasons for gaps.</p><ol><li><p><strong>Easy edits</strong></p></li></ol><p>First, since Wand Wood <em>could</em> be 12 characters, the database leaves extra space at the end in case you edit the wood later to be a longer name. Imagine if you just stored <em>Harry Potter</em>&nbsp;in the space for the 12 characters in his name, then in 4th year he decided to change his official name to <em>Harry J. Potter</em> . You would have no space left, so you&#8217;d have to slide all the remaining characters down on disk, meaning rewrite the entire rest of the table after his row! Sounds slow. Better to leave a gap since storage is cheap.</p><ol start="2"><li><p><strong>Easy navigation</strong></p></li></ol><p>It&#8217;s also important for all tuples to take up the same amount of room so that you can easily hop between them. If you wanted to know where row 5 starts and all the rows were a different length, you&#8217;d need to know the size of each row before row 5 and add them up. Now we can just say it&#8217;s <code>4*ROW_LENGTH</code> which is the same for every row, letting you easily navigate the table.</p><p>Within a specific tuple, since the fields are all consistent sizes, you can easily locate a field by looking up the size of each one before it (or a precomputed offset) and hopping to it.</p><ol start="3"><li><p><strong>Memory alignment</strong></p></li></ol><p>Another more subtle reason for the gaps is that data is stored to align to the space it will take in memory, making it faster to read into memory: if the data is aligned to memory size on disk, then you can read data directly into memory from disk without having to shift it around. On modern 64-bit machines, data generally sits in chunks of size 64 bits for fastest access unless you have some pressing reason to try to squeeze in extra data&#8212;like <a href="https://protobuf.dev/programming-guides/encoding/#varints">varints in protobufs</a> trying to reduce the size of data sent over a wire.</p><p>Data in your database will be spaced out so that it aligns into 64-bit chunks and can easily be read into memory for fast access.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://katbusch.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Let's Make Software! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Organizing tuples in a table</h2><p>Going up a layer, hardware storage is generally divided into segments called <em>blocks </em>usually of 512 kilobytes. Blocks are read all at once and easily transferred to memory at once. For this reason, relevant tuples are stored sequentially on a block so they can all be read in at once. And you wouldn&#8217;t want a tuple to start on one block and end on another. If you did that, you would have to read in two whole blocks to read in one tuple. How data is organized within a page is beyond the scope of this article, but here&#8217;s some documentation on how it&#8217;s done in <a href="https://www.postgresql.org/docs/current/storage-page-layout.html">Postgres</a> for those interested. (Though one thing to note for later is there&#8217;s usually some header data associated with each tuple.)</p><p>If a table spans multiple blocks, the blocks will point to each other. &nbsp;It&#8217;s helpful to have all of a given table as close together on disk as possible so that it can all read into memory in as few loads as possible.</p><p>It&#8217;s important to remember that these databases were designed in an era where hard disks prevailed, not SSDs. &nbsp;Hard disks had an interesting property where it took MUCH longer to find a particular block (<em>seek</em>) than to <em>read</em> in data from that block (and any subsequent blocks). While <em>seeks</em> are still longer than reads on SSDs, the difference is much less pronounced&#8212;a seek is ~9ms on a modern HDD vs ~.1 ms on a modern SSD. That means that database reads would be way, way slower if your data that tended to be accessed together was spread out on the disk. Many of the design decisions that went into the major relational databases are vestigial consequences of this old technology.</p><p>That being said, you can tune databases to be more optimized for SSDs. For instance, see <a href="https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-RANDOM-PAGE-COST">random_page_cost</a> and <a href="https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-EFFECTIVE-IO-CONCURRENCY">effective_io_concurrency</a> for Postgres.</p><h1>Some common operations</h1><p>We can go over some simple operations on databases with just the information we have so far.</p><h2>Adding a row</h2><p>Adding a row is pretty simple in a regular table. &nbsp;You navigate to the end of the existing rows (there would usually be a nice pointer to get you here) and write the relevant data.</p><p>Adding a row is much trickier if your database is stored in sorted order. &nbsp;Then you must use a table scan or index lookup (more on this later) to find the relevant place in the database. If there happens to be an empty slot (for example from a previously deleted row), you can add your row, but if not you&#8217;ll need to shift <em>everything</em> that comes after it over (called a<em> <a href="https://www.postgresql.org/docs/current/btree-implementation.html#:~:text=A%20page%20split%20operation%20makes,upwards%E2%80%9D%20in%20a%20recursive%20fashion">page split</a></em>). &nbsp;Fancy pointer magic can avert having to shift things, but remember that might result in more disk seeks which are slow.</p><p>This is why databases are <em>not</em> usually stored sorted and instead rely on indexes to maintain ordering (again, more on indexes shortly!).</p><h2>Deleting a row</h2><p>The logical thing in deleting a row might be to slide everything after it back to conserve space, but remember that as with adding to a sorted table that would mean you have to rewrite the entire table that comes after the row. &nbsp;Instead, a flag in each tuple&#8217;s header is usually just flipped to indicate that row is now deleted. &nbsp;The tuple is now morbidly called a <em>tombstone </em>or <em>dead</em> <em>row</em>. They can later be reused, and if you have a lot, it might be a good idea to consolidate your table, called <em>vacuuming </em>in Postgres and done with <code>OPTIMIZE TABLE</code> in MySQL.</p><h1>Indexes</h1><p>Okay so we&#8217;ve got our tables laid out on disk.&nbsp;Generally, if you want to access data now, you&#8217;ll need to scan the entire table and check each row to see if it matches your search query. This can obviously be very slow for a large table&#8230; Enter indexes.</p><p>Indexes are pretty simple. They store relevant columns in a smaller, faster data structure that you can use to hop quickly to the desired part of your table. As part of designing a database schema, an engineer thinks about which columns and combinations of columns would benefit from this fast lookup structure and creates an index for them. You can always add indexes later on too as you add new columns and data access patterns.</p><p>Indexes are usually stored in a tree structure because they have super fast O(log(n)) lookup. &nbsp;The particular structure that&#8217;s most common is a <a href="https://www.cs.cornell.edu/courses/cs3110/2012sp/recitations/rec25-B-trees/rec25.html">B-tree</a>, though you can <a href="https://www.postgresql.org/docs/current/indexes-types.html">configure this</a>.</p><p>For instance, above in our students table we could add index on year that would let you sort, select, or do cutoffs by student year in O(log(n)). More on this in the next section.</p><p>So to summarize, without an index you need to scan a table to find a row. An index lets you use a fast data structure to look up where your data is without having to scan the whole table. It also lets you walk through data in a sorted order without having to read the whole table into memory or store it sorted.</p><h1>Query planning and execution</h1><p>So now the real purpose of a database: querying! What is the process between <code>SELECT * FROM students</code> to actually getting the data we need?</p><p>This happens in 4 stages:</p><ol><li><p><strong>Parsing</strong>: Parsing is relatively straightforward. Just take the text and turn it into code as any compiler or interpreter would.</p></li><li><p><strong>Query re-writing</strong>: this is the query planning stage, where the sophisticated optimizations can happen to change the basic query you wrote into one that is very speedy. This where a lot of the magic (and confusion!) happens.</p></li><li><p><strong>Physical query plan creation</strong>: Take the logical query plan from step 2 and turn it into a physical query plan: that means what do you actually read from which tables, columns indexes, etc? While step 2 seems like it has most of the &#8220;optimization&#8221;, step 3 actually has some as well. For instance, how should the data be passed between stages: via memory or disk?</p></li><li><p><strong>Execution</strong>: Execute the query and return the results</p></li></ol><p>The query planner generates multiple possible plans and then scores them based on how long it thinks they&#8217;ll take and how many resources they&#8217;ll consume, then it picks the best one.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p>Let&#8217;s dig in a little bit more to steps 2 and 3, collectively called the <em>query optimizer.</em> Let&#8217;s look at a few simple examples. &nbsp;To get a basic sense of why this would be needed, consider the query </p><p><code>SELECT * FROM students WHERE year &gt; 2</code> </p><p>on our students table above. Remember, we have an index on year. So here are two options:</p><ol><li><p>Read every row in students and if the year is &gt; 2, copy that row into our result set. &nbsp;This is called a table scan, and if you&#8217;re scanning tables of any large size and you&#8217;re trying to make an interactive application, it&#8217;s almost always bad to do this because you have to read in the entire table to memory and do computations on it.</p></li><li><p>Use an index on <code>year</code> to find pointers to rows where year &gt; 2, and copy those rows into our result set.</p></li></ol><p>It might seem like there&#8217;s an obvious choice: isn&#8217;t O(log(n)) (for option 2) better than O(n)? &nbsp;Often, and that&#8217;s why we make indexes in the first place! &nbsp;But there are a few circumstances where it might not be. &nbsp;What if 99.9% of rows have year &gt; 2? Then it might actually cost us time to read in the index rather than just go straight to the table scan. Or maybe the data is very small, and it&#8217;s faster just to run through the data than hop between the index and the data. Databases actually keep <a href="https://www.postgresql.org/docs/current/planner-stats.html">statistics internally</a> on the contents of tables so that they can make intelligent decisions about these types of tradeoffs.</p><p>The above example is super simple. As queries increase in complexity, so too do query plans. There are several ways to plan join operations, and which one to pick can depend on many factors, from how big the tables are to the amount of memory to the cardinality of different fields.</p><p>The query optimizer can get extremely fancy, and it can sometimes do a bad job. You&#8217;re probably most accustomed to actually looking at the query planner&#8217;s results when it is doing a bad job&#8212;that&#8217;s when a query that should be fast isn&#8217;t and you have to go in and debug why with an <a href="https://dev.mysql.com/doc/refman/8.0/en/using-explain.html">explain</a> <a href="https://www.postgresql.org/docs/current/sql-explain.html">command</a>.</p><p>Finally, since query planning is NP-complete (read: slow), there&#8217;s a tradeoff: how much time do you spend searching for an optimal query vs just going for it? This tradeoff is often configurable.</p><h1>Databases in Practice</h1><p>I hope this guide helps you feel a little more confident delving into the world of relational databases. Relational databases, while simple on the surface, are extremely complex. They have managed to power most software for decades. Postgres has dozens of parameters just to configure the <a href="https://www.postgresql.org/docs/current/runtime-config-query.html">query planner</a>, not to mention <a href="https://www.postgresql.org/docs/current/runtime-config.html">everything else</a>. You&#8217;ll have no shortage of interesting intricacies to delve into as you continue your journey, from <a href="https://martinfowler.com/articles/patterns-of-distributed-systems/wal.html">write-ahead logging</a> to <a href="https://en.wikipedia.org/wiki/Replication_(computing)">replication</a> to <a href="http://infolab.stanford.edu/~ullman/fcdb/aut07/slides/trans-view-index.pdf">transactions</a>.</p><h1>Sources and Further reading</h1><p>I drew from a variety of sources from discussions with experts to reading Wikipedia, but I mostly used these:</p><ul><li><p><a href="https://www.amazon.com/Database-Systems-Complete-Book-2nd/dp/0131873253">Database Systems: The Complete Book</a></p></li><li><p><a href="https://www.postgresql.org/docs/15/index.html">Postgres Documentation</a></p></li></ul><p>In addition to the above, a few other interesting things in database-land:</p><ul><li><p>Read about <a href="https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/36632.pdf">Dremel</a>, a distributed analytics database by Google (the basis for Snowflake, BigQuery, and much of modern OLAP databases)</p></li><li><p>Read about <a href="https://assets.amazon.science/dc/2b/4ef2b89649f9a393d37d3e042f4e/amazon-aurora-design-considerations-for-high-throughput-cloud-native-relational-databases.pdf">Aurora</a>, an innovative use of hardware by AWS</p></li><li><p>Read about <a href="https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf">Spanner</a>, a globally consistent database created at Google</p></li></ul><p>Please leave other recommendations! And huge thanks to my reviewers, Julie Tibshirani, Dylan Visher, and Stuart Cornuelle.</p><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://katbusch.com/p/the-basics-of-relational-databases?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://katbusch.com/p/the-basics-of-relational-databases?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://katbusch.com/p/the-basics-of-relational-databases/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://katbusch.com/p/the-basics-of-relational-databases/comments"><span>Leave a comment</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>As an example, you can read more about Postgres&#8217;s query planning <a href="https://www.postgresql.org/docs/current/planner-optimizer.html">here</a>.</p><p></p></div></div>]]></content:encoded></item><item><title><![CDATA[TypeScript enums explained]]></title><description><![CDATA[This article explains the difference between Typescript&#8217;s enum, const enum, declare enum, and declare const enum identifiers. Caveat: I&#8230;]]></description><link>https://katbusch.com/p/typescript-enums-explained-e5f9a101afc9</link><guid isPermaLink="false">https://katbusch.com/p/typescript-enums-explained-e5f9a101afc9</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Sun, 13 May 2018 21:42:54 GMT</pubDate><content:encoded><![CDATA[<p>This article explains the difference between Typescript&#8217;s enum, const enum, declare enum, and declare const enum identifiers. Caveat: I don&#8217;t recommend you use any of these enums most of the time. See the recommendation section at the end for details.</p><p>You can follow along with these explanations in <a href="https://www.typescriptlang.org/play/#src=%2F%2F%20Transpiles%20to%20a%20lookup%20table%0D%0Aenum%20Cheese%20%7B%20Brie%2C%20Cheddar%20%7D%0D%0A%0D%0A%2F%2F%20Evaluates%20to%200%20at%20runtime%0D%0ACheese.Brie%0D%0A%0D%0A%2F%2F%20Evaluates%20to%20%27Brie%27%20at%20runtime%0D%0ACheese%5B0%5D%0D%0A%0D%0A%2F%2F%20Evaluates%20to%200%20at%20runtime%0D%0ACheese%5B%27Brie%27%5D%0D%0A%0D%0A%2F%2F%20Transpiler%20emits%20nothing%20at%20delcaration%0D%0Aconst%20enum%20Bread%20%7B%20Rye%2C%20Wheat%20%7D%0D%0A%0D%0A%2F%2F%20Emits%200%20at%20compile%20time%0D%0ABread.Rye%0D%0A%0D%0A%2F%2F%20Compiler%20error.%20const%20enums%20don%27t%20provide%20reverse%20lookup.%0D%0A%2F%2FBread%5B0%5D%0D%0A%0D%0A%2F%2F%20Emits%20nothing%20because%20it%27s%20expecting%20a%20declaration%20elsewhere%0D%0Adeclare%20enum%20Wine%20%7B%20Red%2C%20Wine%20%7D%0D%0A%0D%0A%2F%2F%20Emits%20Wine.Red%20and%20Wine%5B0%5D%20but%20these%20will%20error%20out%20at%20runtime%0D%0A%2F%2F%20without%20a%20declaration%20elsewhere%0D%0AWine.Red%0D%0AWine%5B0%5D%0D%0A%0D%0A%2F%2F%20Emits%20nothing%20at%20compile%20time%0D%0Adeclare%20const%20enum%20Fruit%20%7B%20Apple%2C%20Pear%20%7D%0D%0A%0D%0A%2F%2F%20Emits%200%20at%20compile%20time%0D%0AFruit.Apple%0D%0A%0D%0A%2F%2F%20Compiler%20error.%20const%20enums%20don%27t%20provide%20reverse%20lookup.%0D%0AFruit%5B0%5D%0D%0A%0D%0A%2F%2F%20Emits%20a%20lookup%20table%20with%20just%20strings%0D%0Aenum%20Beer%20%7B%0D%0A%20%20%20%20Lager%20%3D%20%27Lager%27%2C%0D%0A%20%20%20%20Ale%20%3D%20%27Ale%27%2C%0D%0A%7D%0D%0A%2F%2F%20Evaluate%20to%20%27Lager%27%20at%20runtime%0D%0ABeer.Lager%0D%0ABeer%5B%27Lager%27%5D%0D%0A%0D%0A%2F%2F%20Emits%20nothing%0D%0Aconst%20enum%20Dip%20%7B%0D%0A%20%20%20%20Guacamole%20%3D%20%27Guacamole%27%2C%0D%0A%20%20%20%20Salsa%20%3D%20%27Salsa%27%2C%0D%0A%7D%0D%0A%2F%2F%20The%20value%20%27Guacamole%27%20is%20inlined%20at%20compile%20time.%0D%0ADip.Guacamole%0D%0A%0D%0A%2F%2F%20String%20literal%20types%20are%20similar%20to%20enums%20and%20easy%20to%20use.%0D%0Atype%20Nut%20%3D%20%27walnut%27%20%7C%20%27almond%27%3B%0D%0Aconst%20nutPrinter%20%3D%20%28nut%3A%20Nut%29%20%3D%3E%20console.log%28nut%29%3B%0D%0A%0D%0AnutPrinter%28%27walnut%27%29%3B%0D%0A%0D%0A%2F%2F%20This%20errors%20out%20at%20compile%20time%20because%20pear%20is%20not%20a%20Nut.%0D%0AnutPrinter%28%27pear%27%29%3B">this TypeScript playground</a>.</p><h3>enum</h3><p><code>enum Cheese { Brie, Cheddar }</code></p><p>First, a plain old enum. The JavaScript transpiler will emit a lookup table. The lookup table looks like this:</p><pre><code>var Cheese;
(function (Cheese) {
  Cheese[Cheese["Brie"] = 0] = "Brie";
  Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));</code></pre><p>Then when you have Cheese.Brie in TypeScript, it emits Cheese.Brie in JavaScript which evaluates to 0. Cheese[0] emits Cheese[0] and actually evaluates to &#8220;Brie&#8221;. The reverse lookup options is unique to enums and can be pretty convenient.</p><h3>const enum</h3><p><code>const enum Bread { Rye, Wheat }</code></p><p>No code is actually emitted for this! Its values are inlined. The following variations emit the value 0 itself in JavaScript:</p><pre><code>Bread.Rye
Bread['Rye']</code></pre><p>Inlining might be useful for performance reasons, although as with all performance optimizations be sure to take note of the trade-off of readability that you&#8217;re signing up for.</p><p>But what about Bread[0]? This will error out at runtime and your compiler should catch it. There&#8217;s no lookup table and the compiler doesn&#8217;t inline here.</p><p>Note that in the above case, the <code>--preserveConstEnums</code>flag will cause Bread to emit a lookup table. Its values will still be inlined though.</p><h3>declare enum</h3><p><code>declare enum Wine { Red, Wine }</code></p><p>As with other uses of declare, declare emits no code and expects you to have defined the actual code elsewhere. This enum version emits no lookup table.</p><p><code>Wine.Red</code> emits <code>Wine.Red</code> in JavaScript, but there won&#8217;t be any Wine lookup table to reference so it&#8217;s an error unless you&#8217;ve defined the actual enum elsewhere.</p><h3>declare const&nbsp;enum</h3><p><code>declare const enum Fruit { Apple, Pear }</code></p><p>This emits no lookup table, but it does inline! Fruit.Apple emits 0.</p><p>But as with const enum, <code>Fruit[0]</code>will error out at runtime because it&#8217;s not inlined and there&#8217;s no lookup table.</p><h3>strings enums and const string&nbsp;enums</h3><p>As of <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-4.html">TypeScript 2.4</a>, you can also create string enums. String enums are like standard int enums but you specify string initializers when you declare your enum.</p><pre><code>enum Beer {
    Lager = 'Lager',
    Ale = 'Ale',
}</code></pre><p>The above string enum will generate this lookup table in Javascript:</p><pre><code>var Beer;
(function (Beer) {
    Beer["Lager"] = "Lager";
    Beer["Ale"] = "Ale";
})(Beer || (Beer = {}));</code></pre><p>So <code>Beer.Lager</code> and <code>Beer['Lager&#8217;]</code> both evaluate to <code>'Lager'</code>. There are no longer any numbers associated with your enum at all.</p><p>Just as with number enums, you can declare a const string enum that will inline the string literals themselves in transpiled JavaScript.</p><h3>Recommendation</h3><p>So of the many types of enums which do I recommend using?</p><p>Trick question: none.</p><p>I recommend <a href="http://www.typescriptlang.org/docs/handbook/advanced-types.html">string literal types</a>. It&#8217;s more TypeScripty. It&#8217;s extremely readable. You can see it in the code as is. If you print the value of a variable you won&#8217;t get a mysterious 0 or 1; you&#8217;ll get the actual string itself every time.</p><p>Because of that, it&#8217;s also easily JSONifiable and produces JSON that you can read in other languages without having to maintain the enum mapping in other languages.</p><p>It also lets you easily do cool TypeScript things like <a href="http://www.typescriptlang.org/docs/handbook/advanced-types.html">mapped types</a>.</p><p>Here&#8217;s an example:</p><p><code>type Nut = 'walnut' | 'almond';</code></p><p>Now, just as with enums, if your function accepts Nut, the compiler will error when a different string is passed in. Check out <a href="https://www.typescriptlang.org/play/#src=%2F%2F%20Transpiles%20to%20a%20lookup%20table%0D%0Aenum%20Cheese%20%7B%20Brie%2C%20Cheddar%20%7D%0D%0A%0D%0A%2F%2F%20Evaluates%20to%200%20at%20runtime%0D%0ACheese.Brie%0D%0A%0D%0A%2F%2F%20Evaluates%20to%20%27Brie%27%20at%20runtime%0D%0ACheese%5B0%5D%0D%0A%0D%0A%2F%2F%20Evaluates%20to%200%20at%20runtime%0D%0ACheese%5B%27Brie%27%5D%0D%0A%0D%0A%2F%2F%20Transpiler%20emits%20nothing%20at%20delcaration%0D%0Aconst%20enum%20Bread%20%7B%20Rye%2C%20Wheat%20%7D%0D%0A%0D%0A%2F%2F%20Emits%200%20at%20compile%20time%0D%0ABread.Rye%0D%0A%0D%0A%2F%2F%20Compiler%20error.%20const%20enums%20don%27t%20provide%20reverse%20lookup.%0D%0A%2F%2FBread%5B0%5D%0D%0A%0D%0A%2F%2F%20Emits%20nothing%20because%20it%27s%20expecting%20a%20declaration%20elsewhere%0D%0Adeclare%20enum%20Wine%20%7B%20Red%2C%20Wine%20%7D%0D%0A%0D%0A%2F%2F%20Emits%20Wine.Red%20and%20Wine%5B0%5D%20but%20these%20will%20error%20out%20at%20runtime%0D%0A%2F%2F%20without%20a%20declaration%20elsewhere%0D%0AWine.Red%0D%0AWine%5B0%5D%0D%0A%0D%0A%2F%2F%20Emits%20nothing%20at%20compile%20time%0D%0Adeclare%20const%20enum%20Fruit%20%7B%20Apple%2C%20Pear%20%7D%0D%0A%0D%0A%2F%2F%20Emits%200%20at%20compile%20time%0D%0AFruit.Apple%0D%0A%0D%0A%2F%2F%20Compiler%20error.%20const%20enums%20don%27t%20provide%20reverse%20lookup.%0D%0AFruit%5B0%5D%0D%0A%0D%0A%2F%2F%20Emits%20a%20lookup%20table%20with%20just%20strings%0D%0Aenum%20Beer%20%7B%0D%0A%20%20%20%20Lager%20%3D%20%27Lager%27%2C%0D%0A%20%20%20%20Ale%20%3D%20%27Ale%27%2C%0D%0A%7D%0D%0A%2F%2F%20Evaluate%20to%20%27Lager%27%20at%20runtime%0D%0ABeer.Lager%0D%0ABeer%5B%27Lager%27%5D%0D%0A%0D%0A%2F%2F%20Emits%20nothing%0D%0Aconst%20enum%20Dip%20%7B%0D%0A%20%20%20%20Guacamole%20%3D%20%27Guacamole%27%2C%0D%0A%20%20%20%20Salsa%20%3D%20%27Salsa%27%2C%0D%0A%7D%0D%0A%2F%2F%20The%20value%20%27Guacamole%27%20is%20inlined%20at%20compile%20time.%0D%0ADip.Guacamole%0D%0A%0D%0A%2F%2F%20String%20literal%20types%20are%20similar%20to%20enums%20and%20easy%20to%20use.%0D%0Atype%20Nut%20%3D%20%27walnut%27%20%7C%20%27almond%27%3B%0D%0Aconst%20nutPrinter%20%3D%20%28nut%3A%20Nut%29%20%3D%3E%20console.log%28nut%29%3B%0D%0A%0D%0AnutPrinter%28%27walnut%27%29%3B%0D%0A%0D%0A%2F%2F%20This%20errors%20out%20at%20compile%20time%20because%20pear%20is%20not%20a%20Nut.%0D%0AnutPrinter%28%27pear%27%29%3B">the playground</a> to see that in action.</p><h4>But what if I have to use an&nbsp;enum?</h4><p>If you go with an enum because you need it for some legacy reason, I recommend a string enum if that fits your needs. It&#8217;s the most similar to a string literal type. If not, a plain old enum will likely cause the least confusion. If you need to go with enums for performance reasons and you&#8217;re truly <a href="https://hackernoon.com/the-rules-of-optimization-why-so-many-performance-efforts-fail-cf06aad89099">sure that&#8217;s your bottleneck</a>, const enum is the way to go.</p>]]></content:encoded></item><item><title><![CDATA[So your website is slow? Let’s fix that.]]></title><description><![CDATA[A practical introduction to web performance]]></description><link>https://katbusch.com/p/so-your-website-is-slow-lets-fix-that-7c4e9e6a7131</link><guid isPermaLink="false">https://katbusch.com/p/so-your-website-is-slow-lets-fix-that-7c4e9e6a7131</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Thu, 09 Nov 2017 05:12:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TVgq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TVgq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TVgq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 424w, https://substackcdn.com/image/fetch/$s_!TVgq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 848w, https://substackcdn.com/image/fetch/$s_!TVgq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!TVgq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TVgq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TVgq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 424w, https://substackcdn.com/image/fetch/$s_!TVgq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 848w, https://substackcdn.com/image/fetch/$s_!TVgq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!TVgq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F07ca95f2-f2e4-4534-97c0-221f7067608b_691x428.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>A lot of websites are slow.</p><p>Many web developers don&#8217;t even know our websites slow because we&#8217;re on fast internet with great hardware and we live close to our servers.</p><p>But what if you have users on the opposite side of the globe from your server? What if their bandwidth is miniscule? What if they&#8217;re on a phone? It could take them ages to load your site. And users&#8217; slow CPUs might spend an eternity computing the next animation frame.</p><p>What&#8217;s an engineer to do? Here I&#8217;ll introduce how to think about web performance and how to go about identifying and fixing web performance problems. This article will familiarize you with web performance concepts. I&#8217;ll point you in the right direction so you can figure out where to get started and where to invest your time when you&#8217;re tackling your website&#8217;s performance.</p><h3>What is a page&nbsp;load?</h3><p>To understand why websites can be slow, let&#8217;s go through the stages of everything that goes into loading a web page. This article focuses on page load speed, not how responsive your website is once it&#8217;s done loading, but some of the same tools apply to both stages.</p><h3>Getting connected</h3><p>Before you can start loading any website, you&#8217;ve got to open a connection between the browser and the target site. This includes DNS lookup to find your website&#8217;s IP address; a TCP handshake to establish a connection; and an SSL handshake to set up encryption (I hope!). That means you&#8217;ve already got several round trips to and from your servers before the user even begins to load your content. If your servers are on the other side of the planet from your client, each round trip is going to be over 100 milliseconds. You can&#8217;t beat the speed of light! If you&#8217;re looking for a page load of less than a second, you already have slashed off almost a third of your time with the three round trips needed to initiate a secure connection.</p><p>Note that there&#8217;s a quick fix here if your stack is up to date. HTTP/2 can use caching to reduce SSL setup to only one round trip&#8202;&#8212;&#8202;just one of many reasons to consider HTTP/2 if you don&#8217;t use it already.</p><h3>Server response</h3><p>Okay, you&#8217;ve connected! Now your servers have to start doing some work to deliver bytes to the user. Depending on the design and nature of your service, response time can vary wildly. Is your website a bit of static content, or do you need to do all sorts of database lookups and computation to prepare a response? Do you compute the whole page and push it at once, or do you send a shell and push other content as it becomes available? Are you rendering React on the server? Your server response could take anywhere from a few milliseconds to hundreds of milliseconds or more.</p><h3>Content download</h3><p>So some sort of response has been prepared. Now clients have to download that response from your servers. Transferring a large response could take a while on a slow or flakey connection.</p><p>The browser can quickly begin to parse the response. As soon as it starts parsing, it&#8217;ll probably receive instructions to go and download a bunch more stuff: <a href="https://hackernoon.com/tagged/css">CSS</a>, images, and <a href="https://hackernoon.com/tagged/javascript">JavaScript</a>. At this point, many websites tap content delivery networks (CDNs) that are experts at delivering static content quickly around the globe.</p><p>And thus the great download of JavaScript, CSS, and images begins. Some large modern websites tend to measure their JavaScript alone in hundreds of kilobytes to megabytes, even after compression. If you have a 100 megabit connection, that will only take tens or hundreds of milliseconds. But if you&#8217;re on a 5 megabit connection (like some mobile networks), you&#8217;re looking at over a second of content download time, and if you&#8217;re on a slower or flakier network, this could take many seconds.</p><h3>Parsing and execution</h3><p>Luckily, the browser usually parallelizes this download step with parsing and execution. Once your CSS is in, rendering begins even if JavaScript is still loading. When any JavaScript comes down, the browser begins to go through the rather expensive tasks of parsing and then executing it. It will also lazily parse JavaScript&#8202;&#8212;&#8202;parsing is CPU intensive and on a slow client it can add seconds to page load time.</p><h3>Everything else</h3><p>Your JavaScript might kick off requests to your server that download more data. Websites can have post-load pipelines to show you more stories on your newsfeed, more products on your store, load a new menu, download higher res images, etc. You might have some CPU-heavy JavaScript computations that need to be done while your user is interacting with your page, like if you have too much going on in a React render function. Maybe you offload some decoding to a Web Worker. Websites diverge a lot after the initial page load.</p><h3>What to do about&nbsp;it</h3><p>Wow! That&#8217;s a lot of stuff that goes on just to load a page! The good news is there&#8217;s a lot of great tools to help you dig in and understand your performance.</p><h3>Profile, profile,&nbsp;profile</h3><p>You might be tempted after reading this to go tear out a bunch of JavaScript code because your number of kilobytes of JavaScript seems high. Stop!</p><p><a href="https://katbusch.substack.com/p/the-rules-of-optimization-why-so-many-performance-efforts-fail-cf06aad89099">As with any performance problem</a>, the first step is simply to profile. No point putting your codebase through the ringer if it turns out performance isn&#8217;t a pressing problem for you.</p><p>You need to understand your performance so that you can decide whether it&#8217;s a problem. There are a lot of great tools for profiling web pages. On your own machine you can use the <a href="https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference">Chrome</a>, <a href="https://developer.mozilla.org/en-US/docs/Tools/Performance">Firefox</a> and <a href="https://docs.microsoft.com/en-us/microsoft-edge/f12-devtools-guide/performance">Edge</a> profilers. These can give you an idea of how much time is spent in various stages of the page load, from network requests to JavaScript execution. Sometimes performance will vary wildly from one browser to the next because of implementation differences.</p><p>You can use these browser dev tools to <a href="https://developers.google.com/web/tools/chrome-devtools/network-performance/reference#throttling">emulate slower connections</a>, so you can see what some of your users might be experiencing. Pretending to be on 2G is a fun one.</p><p>Make sure you test your page with static resources both <a href="https://stackoverflow.com/questions/14969315/whats-the-difference-between-normal-reload-hard-reload-and-empty-cache-a">uncached and cached</a>.</p><p>You can also use tools like <a href="https://www.webpagetest.org/">webpagetest</a> to see what page loads are like from different browsers and different places around the world.</p><h3>Log, log,&nbsp;log</h3><p>On top of that, strongly consider logging page load times for your users. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API">Navigation Timing API</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API">Resource Timing API</a> are your friends. Send the information from these APIs on your users&#8217; machines to your servers and collect it for analysis.</p><p>But careful when interpreting results: these APIs measure many different stages of your page load. Make sure you figure out <a href="https://www.stevesouders.com/blog/2015/08/07/dominteractive-is-it-really/">which event</a> actually lines up with when the user can view or interact with your page.</p><p>This user data will let you see the actual performance real users are experiencing and help you get a breakdown of where time is being spent. Logging will help you home in on problems by looking at which countries, browsers, and devices are suffering.</p><p><a href="https://engineering.linkedin.com/blog/2017/10/sleek-and-fast--speeding-up-your-fat-web-client">Some teams</a> find it helpful to log performance metrics on every commit or run perf tests on every commit to identify degrading performance quickly. After your profiling, you probably have a good idea of which metrics you need to track and log for each commit: bytes of JavaScript, number of images, etc.</p><h3>Now for the fun part: make your page&nbsp;fly</h3><p>If your investigations reveal that your website is slower than you want to be in a way that you believe is impacting user experience, it&#8217;s time to get to work. Your profiling information should already have revealed to you where to get started.</p><p>There are lots of small changes that can speed things up: <a href="https://developers.google.com/speed/docs/insights/AvoidRedirects">remove redirects</a>, <a href="https://www.w3.org/TR/resource-hints/#preconnect">preconnect to the CDN</a>, etc.</p><p>For many web pages, the main thing you can do to make your website faster is load less stuff. <a href="https://redfin.engineering/i-watched-all-of-the-chrome-dev-summit-2017-videos-so-you-dont-have-to-9b62a593c3cb">Google recommends</a> you keep your page weight to 1 megabyte uncompressed.</p><p>Minify JavaScript, compress your content, turn on <a href="https://webpack.js.org/guides/tree-shaking/">tree shaking</a>. Use <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script">async and defer</a> whenever possible to load scripts that aren&#8217;t needed right away. Remove unnecessary images. Load only the JavaScript you actually need. Remove code for inaccessible features. Redesign your page to load fewer images! Lots of huge images on your homepage might look really snazzy on your fast office internet, but what does it look like on a slower connection where they load pixel by pixel?</p><p>But be careful to keep the big picture in mind instead of only adding on some hacks that will make your code more complicated and will break in a few months. <a href="http://engineering.khanacademy.org/posts/js-packaging-http2.htm">Upgrading to HTTP/2.0 won&#8217;t solve all your problems</a> if you&#8217;re just loading too much data. On the other hand, a thoughtful rearchitecting to make your website&#8217;s performance stable for a long period might be the best investment of your time. For instance, you might want to make it so that your page&#8217;s components load <a href="https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919/">in parallel</a>, <a href="https://open.nytimes.com/react-relay-and-graphql-under-the-hood-of-the-times-website-redesign-22fb62ea9764">use GraphQL</a> to load only necessary data, or implement <a href="https://medium.com/netflix-techblog/making-netflix-com-faster-f95d15f2e972">server-side rendering</a>.</p><h3>Eyes on the prize: user experience</h3><p>But don&#8217;t miss the forest for the trees. Your end goal should always be focused on your users and improving their experience. Performance is just one aspect of the overall experience of your website. If you&#8217;re smart about your development process, you&#8217;ll make a page that&#8217;s not only snappy but also delightful to use.</p>]]></content:encoded></item><item><title><![CDATA[Quick Tips for Gitting on a Team]]></title><description><![CDATA[A series on best practices in large codebases for new engineers. This one focuses on version control etiquette.]]></description><link>https://katbusch.com/p/quick-tips-for-gitting-on-a-team-33533d75aea9</link><guid isPermaLink="false">https://katbusch.com/p/quick-tips-for-gitting-on-a-team-33533d75aea9</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Sat, 17 Jun 2017 22:44:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!N_ba!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>A series on best practices in large codebases for new engineers. This one focuses on version control etiquette.</em></p><p>If you&#8217;re new to using <a href="https://hackernoon.com/tagged/git">git</a> on a team instead of solo, this super fast primer will help you get started with standard workflows and etiquette.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N_ba!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N_ba!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N_ba!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N_ba!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N_ba!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N_ba!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N_ba!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N_ba!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N_ba!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N_ba!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2057e7-c3c2-4176-b752-51538695cb5f_800x1067.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Hopefully not what your commit history looks like! By austrini [CC BY 2.0 (<a href="http://creativecommons.org/licenses/by/2.0">http://creativecommons.org/licenses/by/2.0</a>)], via Wikimedia Commons</figcaption></figure></div><h3>Use feature&nbsp;branches</h3><p>Use <a href="https://guides.github.com/introduction/flow/">feature branches</a>. Don&#8217;t push your <a href="https://hackernoon.com/tagged/work">work</a> directly to the master branch until it&#8217;s finalized and (depending on your team&#8217;s culture) code reviewed. This is basic and discussed in many great tutorials so I won&#8217;t go into detail, but it&#8217;s a precursor to pretty much all work in larger teams.</p><h3>Don&#8217;t mix bug fixes with feature&nbsp;work</h3><p>If there&#8217;s a bug in the master branch, it&#8217;s a good idea to create a new branch (perhaps with a name starting with &#8220;fix/&#8221;) and file a pull request. An independent branch ensures that the fix can be merged independently of other work. If you tack it on to a pull request for a less important feature you might hold up others who are waiting for the fix and create many angry team members.</p><h3>Write clear commit&nbsp;messages</h3><p>When you&#8217;re working on a team, the repo&#8217;s commit history is a vital record of the progress of a project and an essential tool for finding bugs and understanding code. In three weeks or three years, somebody (even you!) might look back at your commit message to figure out why you wrote something. Help them out! Try to make your commit messages as clear and concise as possible.</p><p>Not clear:</p><blockquote><p>&#8220;Fix bug with colors&#8221;</p></blockquote><p>Clear:</p><blockquote><p>&#8220;Fix issue #16: make send button change colors on press&#8221;</p></blockquote><p>Not very specific:</p><blockquote><p>&#8220;Add list endpoint&#8221;</p></blockquote><p>Specifies the relevant part of the repo:</p><blockquote><p>&#8220;[Photos controller] Add functionality to list recent photos&#8221;</p></blockquote><h3>Squash commits before&nbsp;merging</h3><p>Squashing commits is the process of taking several commits and rewriting them into one commit. Squashing creates a clean commit history. When you and your teammates are looking back at the log on master, instead of seeing a jumble of crap like this:</p><blockquote><p>Thu May 18 13:52 lint errors</p></blockquote><blockquote><p>Mon May 22 14:47 Feedback from CR</p></blockquote><blockquote><p>Thu May 18 16:04 Lololol oops</p></blockquote><blockquote><p>Thu May 18 16:03 Fix tests</p></blockquote><blockquote><p>Thu May 18 13:52 Fix lint errors</p></blockquote><blockquote><p>Thu May 18 13:39 Forgot to add file</p></blockquote><blockquote><p>Thu May 18 13:36 Start on compression</p></blockquote><p>They&#8217;ll only see one commit with a clear description of your change:</p><blockquote><p>Mon May 22 15:47 Add video compression option to uploader</p></blockquote><p>Squashing means you can feel free to have lots of crazy work-in-progress commits on your feature branch. Just clean it up with a squash before submitting your pull request and/or before merging with master.</p><p>There are <a href="https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git">several ways</a> to squash with git. GitHub actually now allows you to squash commits with the GitHub UI. If your repo has the correct settings turned on you&#8217;ll see a &#8220;squash and merge&#8221; button on your pull request.</p><h3>Rebase before&nbsp;merging</h3><p>Consider rebasing your branch on top of master before you merge. If you merge, you&#8217;re combining master and your branch by creating a new commit that corrects conflicts:</p><blockquote><p>[on master] Merge branch &#8216;video_compression&#8217;</p></blockquote><blockquote><p>[from your branch, now on master] Add video compression option to uploader</p></blockquote><blockquote><p>[from master] Fix issue #17: remove uploader exit button</p></blockquote><p>You could end up with a lot of merge commits this way. If instead you rebase on top of master, you correct the merge conflicts in your commit and end up with this cleaner history once you merge:</p><blockquote><p>[from your branch, now on master] Add video compression option to uploader</p></blockquote><blockquote><p>[from master] Fix issue #17: remove uploader exit button</p></blockquote><p>One warning: it&#8217;s always better to squash THEN rebase instead of rebase then squash. Git rebases commits one by one. If you haven&#8217;t squashed, you&#8217;ll need to resolve conflicts in every commit it in your branch with master. If you squash, you&#8217;ll only need to resolve one commit&#8217;s conflicts.</p><h3>And that&#8217;s&nbsp;it!</h3><p>Keep in mind that these tips all describe fairly common practices, but customs vary from team to team. If you&#8217;re working on a codebase with an existing repo, ask your teammates about their git conventions.</p><p>If you have any other tips for gitting on a team, feel free to leave them in a response.</p><p>Article 1: <a href="https://katbusch.substack.com/p/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f">Trust No One: An Introduction to Large Codebases for New Engineers</a></p><p>Article 2: <a href="https://katbusch.substack.com/p/treat-yourself-e55a7c522f71">A Beginner&#8217;s Guide to Automated Testing</a></p><p>Article 3: <a href="https://katbusch.substack.com/p/quick-tips-for-gitting-on-a-team-33533d75aea9">Quick Tips for Gitting on a Team</a></p>]]></content:encoded></item><item><title><![CDATA[The Software Engineer’s Essential Time Estimation Guide]]></title><description><![CDATA[Hofstadter&#8217;s Law: It always takes longer than you expect, even when you take into account Hofstadter&#8217;s Law. &#8212; Douglas Hofstadter]]></description><link>https://katbusch.com/p/a-software-engineers-essential-time-estimation-guide-d7328238c510</link><guid isPermaLink="false">https://katbusch.com/p/a-software-engineers-essential-time-estimation-guide-d7328238c510</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Sat, 25 Feb 2017 01:04:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cfov!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Hofstadter&#8217;s Law: It always takes longer than you expect, even when you take into account Hofstadter&#8217;s Law.&#8202;&#8212;&#8202;<a href="https://en.wikipedia.org/wiki/Douglas_Hofstadter" title="Douglas Hofstadter">Douglas Hofstadter</a></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cfov!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cfov!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 424w, https://substackcdn.com/image/fetch/$s_!cfov!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 848w, https://substackcdn.com/image/fetch/$s_!cfov!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 1272w, https://substackcdn.com/image/fetch/$s_!cfov!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cfov!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/76e63869-7477-4df9-928f-47ae32f5505b_512x538.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cfov!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 424w, https://substackcdn.com/image/fetch/$s_!cfov!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 848w, https://substackcdn.com/image/fetch/$s_!cfov!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 1272w, https://substackcdn.com/image/fetch/$s_!cfov!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F76e63869-7477-4df9-928f-47ae32f5505b_512x538.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">By Rogerborrell (Own work) [CC BY-SA 4.0 (<a href="http://creativecommons.org/licenses/by-sa/4.0">http://creativecommons.org/licenses/by-sa/4.0</a>)], via Wikimedia Commons</figcaption></figure></div><p>A Product Manager friend of mine recently told me about a problem she was having: &#8220;<a href="https://hackernoon.com/tagged/software">Software</a> engineers can never estimate how long their projects will take. What can I do?&#8221; Two CEOs recently told me the same thing.</p><p>We engineers have all witnessed this too. I once saw a project estimated at two days take four months. In that case even the &#8220;just double it&#8221; heuristic would <em>still</em> be off by an order of magnitude. This can have real implications for the business. I&#8217;ve seen a whole company move mountains for a launch event that had to be pushed out months.</p><p>At a high level the problem is a difference between what engineers mean when we estimate time and what PMs, managers, PR, and really everybody else mean. Most engineers instinctively think about the minimum time to write a working prototype if everything goes pretty much as planned. But those blocked downstream want to know when the project will be <strong>ready for launch</strong>&#8202;&#8212;&#8202;and that&#8217;s a totally different story.</p><p>For engineers, mastering estimation is a lifelong journey. Neglecting it will plague you and everyone you directly or indirectly interface with. Mastering estimation will set you apart and your colleagues will associate you with professionalism, stability, and quality work.</p><h3>Why we need to&nbsp;estimate</h3><p>Let me start by answering the question I most often get from engineers: &#8220;Why bother?&#8221; Many engineers complain (correctly) that it&#8217;s an overhead cost. &#8220;I&#8217;ll finish sooner if I just power through on it until it&#8217;s done!&#8221;</p><p>There are two main reasons: external dependencies and prioritization.</p><h4>External dependencies</h4><p>Nothing impactful operates in a vacuum. Projects often have external dependencies like coordination with non-engineering teams (comms, finance, PR, customer support), other <a href="https://hackernoon.com/tagged/engineering">engineering</a> teams, or even end-users themselves. It&#8217;s typically the job of the manager, PM, or CEO to coordinate with these external dependencies. That means that the one who is best qualified to make a time estimate (the engineer) isn&#8217;t the one who needs it most. This asymmetry leads to a fundamental tension.</p><h4>Prioritization</h4><p>Time estimates are also key for prioritizing work. &#8220;Bang for the buck&#8221; is an important metric in engineering and there&#8217;s no &#8220;buck&#8221; without real estimation. Even if the feature you&#8217;re working on is the most awesome thing in the world, if you take the time to do a full estimate, you might realize it will take way too long to finish.</p><p>Say you&#8217;re working on a project that will make the website 50% faster but in the same amount of time you could have finished two projects that will each make the website 40% faster. If you don&#8217;t take time to do an initial estimate, you&#8217;ll never know that you could have ended up with a much faster website!</p><h3>Time estimation 101</h3><p>Now that we all agree that time estimation is necessary the vast majority of the time, let&#8217;s talk about techniques.</p><p>We underestimate time because we think &#8220;How long would it take me to write a basic version of this?&#8221;</p><p>But shipping is much more than a basic version. You will need to account for the time it takes you to write, test, debug, and polish. Don&#8217;t forget the time you&#8217;ll be in meetings, interviews, doing code reviews, sending emails, etc.</p><p>Another reason we underestimate is that we almost always encounter &#8220;unknown unknowns&#8221; in the coding process itself. And those are impossible to anticipate fully and account for. Maybe your IDE will get an update that breaks your project and you&#8217;ll burn a day fixing it. There&#8217;s no way your estimate could have taken that into account.</p><p>But we can still do much better than our initial instincts. Here&#8217;s what I do:</p><h4>Step 1: Make a technical plan</h4><p>You should already have a technical plan or design doc ironed out for any nontrivial project before you begin. You use this to let others know what you&#8217;re doing and get feedback. The technical plan is the ideal place to start the time estimate. As you work through the technical details, you&#8217;ll already magically be improving your estimate as you uncover unknown unknowns. Maybe you&#8217;ll realize that you probably will need to upgrade to a new version of a library that you&#8217;re using and that could add a day. You might even realize the library you were planning to use doesn&#8217;t actually exist and you&#8217;ll need to write it.</p><p>Granularity is important here. If any step feels murky or vague you&#8217;re either hand-waving (and should learn more) or you need to break it down into smaller steps. At the same time if a step is too fine-grained it might be brittle enough to invalidate the whole plan in practice.</p><p>For a good guide on what sort of thinking should go into your technical plan, check out <a href="https://medium.com/@owlishness/what-do-you-mean-we-need-more-time-c4b7120aa707">this article</a> by <a href="https://medium.com/u/64c63c7efb34">Alicia Chen</a>. One key point is to iron out any potential ambiguities with the PM or other stakeholders so that you don&#8217;t end up building the wrong thing and having to start over.</p><h4>Step 2: Add a time estimate to each&nbsp;step</h4><p>Estimate how long each step in your technical plan will take to implement. This will often involve research into the details (&#8220;is there already a library to do this or not?&#8221;). Depending on the nature of the project, throwing together a simple prototype might help reveal a lot of potential future pain points.</p><h4>Step 3: Add in a bunch of extra&nbsp;time</h4><p>Now that you have a barebones of your estimate, there are all those things we mentioned earlier to account for.</p><ul><li><p>Debugging as you go: Bugs always come up. A lot of this depends on your experience with a specific codebase and the codebase&#8217;s maturity.</p></li><li><p>Meetings, interviews, vacations, etc: You probably won&#8217;t be at your desk coding the whole time. How many hours will you REALLY have to code? You should at least look at your calendar when estimating.</p></li><li><p>Final testing and bug-bashing: You should generally be <a href="https://hackernoon.com/treat-yourself-e55a7c522f71">writing tests as you go</a>, but a lot of teams need to do an extra round of polish work or integration testing before launch. Give ample budget for it in your estimates. If you&#8217;re doing a staged rollout, the initial 1% rollout will probably reveal bugs that need fixing. Account for that.</p></li><li><p>Code review: How many rounds are typical for you in this codebase? How long do they usually take? Be sure to verify an ample supply of reviewers (and maybe check their calendars). If it&#8217;s the kind of project where there&#8217;s only one possible reviewer you should solicit a commitment in advance and ask them for a backup in case they&#8217;re on vacation or way too busy at a critical point.</p></li></ul><p>Once you start adding in all of these costs to shipping, you&#8217;ll start to see your time estimates match up a lot better with when your projects <em>actually launch</em>. Yes, they&#8217;ll be longer. Yes, you might feel pressured to shorten them. But when people figure out they can depend on you they&#8217;ll come to appreciate your estimates.</p><h4>Step 4: Review your estimate after you&#8217;ve&nbsp;launched</h4><p>Yes, it sounds like a pain to go back to your time estimate after you&#8217;ve finished a project and review what you&#8217;ve done. But this review is how you learn and get better next time.</p><p>What ended up taking a different amount of time from expected? If integration testing took twice as long as you thought, write that down and leave more time for it next time. Or try to improve your integration testing system.</p><p>You&#8217;ll definitely see your estimates improve with time. You might even come up with some great insights here that will help your whole team.</p><h3>In the end, it&#8217;s all about communication</h3><p>Communicate your timetable and changes to it early and often. If you let your manager know a month before launch that there&#8217;s a new security bug in the library you were using and you&#8217;ll have to start over from scratch, they&#8217;ll in turn have time to notify PR, finance, or users that there&#8217;ll be a delay.</p><p>Communication to relevant parties also lets them give you important information that can affect your estimate. A designer might say, &#8220;Oh, if that fancy animation is going to take a whole week we can just cut it completely.&#8221; A PM might add, &#8220;This is just a prototype to experiment on in user studies. We don&#8217;t need to do much bug bashing for this iteration.&#8221; A manager might say, &#8220;You&#8217;re spending half your time in meetings? Let me fix that!&#8221;</p><p>For engineers, don&#8217;t give in to pressure to report a shorter time than is realistic to appease the higher-ups. It is more professional to be honest about your estimates and how they&#8217;re changing.</p><p>For everybody else involved, respect that estimation is hard and that it&#8217;s going to be a process. You can only cut down time estimates by sitting down and removing features or stages that aren&#8217;t actually going to be needed for launch. You can&#8217;t cut it down by nagging.</p><p>We&#8217;re never going to be able to perfectly estimate time of a project. The only way around this is an open communication, compassion, and relentless prioritization.</p>]]></content:encoded></item><item><title><![CDATA[A Beginner’s Guide to Automated Testing]]></title><description><![CDATA[The how and why of software testing in large codebases]]></description><link>https://katbusch.com/p/treat-yourself-e55a7c522f71</link><guid isPermaLink="false">https://katbusch.com/p/treat-yourself-e55a7c522f71</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Sat, 03 Dec 2016 20:32:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!XA71!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is the second entry in my series on navigating large codebases for new software engineers. It&#8217;s not necessary to read the first article to understand this one, but you can find it <a href="https://medium.com/@katbusch/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f#.a5l573tdp">here</a>.</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XA71!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XA71!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XA71!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XA71!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XA71!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XA71!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/c9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XA71!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XA71!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XA71!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XA71!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ea233e-f4a2-4d4e-9689-d5c220d74cf2_500x333.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p><a href="https://hackernoon.com/tagged/software">Software</a> tests are the best gift an <a href="https://hackernoon.com/tagged/engineer">engineer</a> can give themselves. Tests make your life and everybody else&#8217;s easier.</p><p>When I started as a software engineer I had absolutely no idea why tests were important and I felt pretty lost on where to start. From my experience mentoring and teaching new engineers, I know I&#8217;m not alone. So this is where you start. Here, with real-world examples, I will make the case for automated software testing and I will explain how to get started writing tests in a disciplined and thoughtful way.</p><h3>Part I: The case for&nbsp;testing</h3><h4>Making your life&nbsp;easier</h4><p>Years ago, when I was starting on my first project in my first job out of school, I didn&#8217;t write any tests. I knew that <em>theoretically</em> tests were good (and I&#8217;d written them during my internships) but I didn&#8217;t really have a solid understanding of why. I didn&#8217;t think it mattered much if I didn&#8217;t write tests at all or at least didn&#8217;t write them until the last minute. I was on a deadline and I didn&#8217;t want to get bogged down writing all that unnecessary extra code.</p><p>I was creating a mobile A/B testing system for Dropbox&#8217;s Android app. I submitted the Java component of the system for code review, with a note: &#8220;TODO: write tests&#8221;. As I developed the code, I engaged in an incredibly tedious manual testing process that involved setting up a test server on my computer, installing the app on a test phone, and manually creating an A/B test on the test phone. Obviously I didn&#8217;t test very many code paths because it was just so tedious.</p><p>After much back and forth, the code reviewer said &#8220;You can&#8217;t merge this without tests.&#8221; So I wrote some basic tests and merged it.</p><p>Lo and behold, I soon needed to fix a small bug. I then had to test that everything worked after the fix. &#8220;Hmm&#8230;,&#8221; I thought. &#8220;I&#8217;ve written some tests for this. I suppose I could use those.&#8221; I ran the tests. Within a few seconds, I knew that everything still worked! Not just a single code path (as in a manual test), but all code paths for which I&#8217;d written tests! It was magical. It was <strong>so much faster</strong> than my manual testing. And I knew I didn&#8217;t forget to test any edge cases, since they were all still covered in the automated tests.</p><p>This gets to one of the most important reasons for tests: tests can make your development process faster. If I&#8217;d written those tests earlier then I could have frequently just run the tests in under a minute instead of tediously poking and printlning my code over and over again to verify it was doing what I expected.</p><h4>Preventing new bugs over&nbsp;time</h4><p>For large codebases preventing bugs over time is really the most important part of tests. In codebases with many engineers contributing, you do not necessarily have control over changes to your code and especially changes to code that your code depends on. With dozens of commits a day, just by chance one of those commits will likely inadvertently change a behavior that your code relies on.</p><p>If you haven&#8217;t written tests, then there&#8217;s no reliable way for other coders to know that their commit has impacted yours. Good tests are an explicit signal to other engineers (or a future version of yourself) of your assumptions about the behavior of your code and its dependencies. In an ever-evolving codebase of millions of lines, how else could others possibly know that your important (but only semi-related) code will no longer work because of their change? If your code is still in the codebase a year (or five) after you&#8217;ve committed it and there are no tests for it, bugs <em>will</em> creep in and nobody will notice for a long time.</p><p>I witnessed an obscure but important user-facing feature broken for weeks because of a change in a seemingly unrelated part of the code. It took a build-up of user reports to trickle through customer support to engineering before anybody noticed. Afterwards, the team wrote a post-mortem about how the unclear dependency structure allowed this seemingly unrelated code to break their feature and go unnoticed. The postmortem didn&#8217;t even mention the simple obvious thing that would have avoided the whole breakage in the first place: a test! (Of course better code structure is also very important.)</p><p>Tests are your best weapon against the complexity of large codebases. Although you try to keep your code clean and clear, something will always break. Remember the rule: <strong>If it matters that the code works you should write a test for it</strong>. There is no other way you can guarantee it will work.</p><h3>Part II: Tips for effective testing</h3><p>Here are some basic tips to make your tests maximally useful and minimally painful. I will introduce some testing concepts and terminology. This is by no means complete. There are <a href="https://www.amazon.com/gp/product/0321146530/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0321146530&amp;linkCode=as2&amp;tag=engineeringba-20&amp;linkId=b770575b23eef9d66aaaa021322be0ad">entire</a> <a href="https://www.amazon.com/gp/product/1617290890/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=1617290890&amp;linkCode=%7b%7blinkCode%7d%7d&amp;tag=engineeringba-20&amp;linkId=%7b%7blink_id%7d%7d%22%3eThe%20Art%20of%20Unit%20Testing:%20with%20examples%20in%20C%3c/a%3e%3cimg%20src=%22//ir-na.amazo">books</a> worth reading on the topic, but it&#8217;s a good start.</p><h4>Many small tests vs one big&nbsp;test</h4><p>A lot of times I come across monolithic tests like &#8220;fn integer_tests() {}&#8221; that test fifteen behaviors of Integer in a row (like this <a href="https://github.com/JuliaLang/julia/blob/271b31ddf931528149aeb2a4f0579a7439858575/test/int.jl">example</a>). I recommend having fifteen separate tests here for two reasons.</p><p>The first is purely practical: if that test fails halfway through, you won&#8217;t know whether the second half of your fifteen checks are passing or not because they won&#8217;t be run. This can make it harder to debug. If you know which three of fifteen exactly are failing, you might be able to identify the problem immediately.</p><p>The second reason is a subtler readability issue. With a glob of fifteen checks in a row, it&#8217;s harder for another engineer reading them to grok exactly what&#8217;s being tested. It&#8217;s also harder for them to see whether they need to add a new test case after a change or whether their case is already covered. If there are fifteen separate tests, it&#8217;s pretty easy to read through the neat names:</p><pre><code>def test_integer_addition():
   ...</code></pre><pre><code>
def test_integer_subtraction():
   ...</code></pre><p>It&#8217;s also very easy for readers to look at how each function is set up and add a new test.</p><h4>Make it easy to add new&nbsp;tests</h4><p>It should be obvious to somebody editing your code how to test their changes. I recommend making helper functions in your test file to make set up and tear down simple. If adding a new test case is extremely easy then you can be more confident that your library will have high test coverage over time. But if the author has to spend a lot of time investigating exactly how to create the appropriate inputs, etc, then if they&#8217;re in a time crunch they might decide to skip a test all together. You should always make it easy for people to do the right thing.</p><h4>Unit tests vs integration tests</h4><p><em>Unit tests </em>exercise one piece of code, like a class, module, or function. They shouldn&#8217;t need to set up an entire environment or complex dependencies (like databases). They&#8217;re usually very thorough and very quick to run.</p><p><em>Integration tests</em>, sometimes called <em>UI tests </em>or <em>system tests</em>, will test the end-to-end functionality of your project. They&#8217;re usually slower to run because they have to initialize an environment, and they&#8217;re prone to be flakier because often small changes (like fixing a UI bug) can cause them to fail.</p><p>Both kinds of tests are important, but day-to-day you should focus on unit tests because they&#8217;re more modular and maintainable and they&#8217;re cheaper to run. This usually involves faking things that your code depends on so they don&#8217;t have to be created. That way you can be pretty sure that if a test fails it&#8217;s from your code breaking, not the thing it depends on. These fake things are often called <em>mocks</em>.</p><p>Integration tests are important as a last line of defense because they usually test what the user actually sees, and in the end that&#8217;s what matters. All your unit tests could be working, but there could be a problem where your libraries aren&#8217;t interacting together as expected. You won&#8217;t catch this without an integration test.</p><h4>Which to write first: code or&nbsp;tests?</h4><p>There&#8217;s a formal development methodology called Test-Driven Development that dictates writing tests before any feature code. This can be hard to do, because you really don&#8217;t know exactly what your interface should look like until you start writing it and realize some of your assumptions need to be changed. On the other hand if you&#8217;re not focused on testing during development it&#8217;s easy to end up with code that&#8217;s hard to use and hard to test.</p><p>The sweet spot is to write code and test in parallel. It&#8217;s an iterative process. Bouncing back and forth between your perspective and an outsider&#8217;s in a test helps guarantee your code&#8217;s interface is usable. Think of it like a painter who stands back from the canvas and pretends to be an ordinary viewer. You&#8217;ll find that writing tests as you go makes your interfaces better and makes your code more testable. If you find yourself writing something hard to test, you&#8217;ll notice it early on when there&#8217;s still time to improve the design.</p><h4>Avoid Flakey&nbsp;Tests</h4><p><em>Flakey tests</em> are tests that fail some percent of the time, either because of a rare bug or because there&#8217;s a problem with the way the test is written. Say the test relies on the order things are inserted in a hashmap. Hashmaps are unordered, but maybe the iterator will return them in order 98% of the time. The other 2% of the time your test will fail. This can lead other people to think their change broke the build and waste time hunting bugs that don&#8217;t exist. If your test is flakey do whatever you can to remove the flakiness. To achieve this stability tests should be deterministic. Don&#8217;t depend on a given ordering of threads, and if you need a random number generator make sure to specify a fixed seed in your tests.</p><h4>Test the interface, not the implementation</h4><p>When you&#8217;re testing, it might seem easiest to dig into your interface and test that private variables have the right value at the right time. While this seems convenient, it has some problems:</p><ol><li><p>Your tests will need to be rewritten if the internal implementation is changed. That is, a tree should have the same behavior whether it&#8217;s implemented as a flat array or with pointers. If your tests are accessing the array, then if the implementation is changed to pointers you&#8217;ll need to totally rewrite your tests.</p></li><li><p>Mucking with internals of the interface takes focus away from what really matters: do the functions that the user of this interface will call work as expected?</p></li></ol><p>I&#8217;m not advocating that someone unfamiliar with the code writes the tests. You know the internals and you know what types of edge cases you should test for. You should haven&#8217;t to reach into private data to test that. Reaching in can cause you to butcher your interface and add functionality whose only purpose is to verify state in your test. It&#8217;s better to focus your energy on the correctness of real use-cases.</p><h3>Try it&nbsp;yourself</h3><p>As with all my posts in this series, my goal is to provide new engineers a practical framework for being successful in large codebases. You&#8217;ll still need to build your own intuition and learn from your own mistakes but hopefully hearing about mine will help the process go faster.</p><p>As you grow as engineer, you realize more and more that testing is a great tool, not an annoying overhead. It&#8217;s a way of helping you write your code faster, make fewer mistakes, and avoid bug creep. Next time you write code, set aside time to treat yourself with some tests.</p><p>Article 1: <a href="https://katbusch.substack.com/p/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f">Trust No One: An Introduction to Large Codebases for New Engineers</a></p><p>Article 2: <a href="https://katbusch.substack.com/p/treat-yourself-e55a7c522f71">A Beginner&#8217;s Guide to Automated Testing</a></p><p>Article 3: <a href="https://katbusch.substack.com/p/quick-tips-for-gitting-on-a-team-33533d75aea9">Quick Tips for Gitting on a Team</a></p>]]></content:encoded></item><item><title><![CDATA[The Rules of Optimization: Why So Many Performance Efforts Fail]]></title><description><![CDATA[The First Rule of Program Optimization: Don&#8217;t do it. The Second Rule of Program Optimization (for experts only!): Don&#8217;t do it yet. &#8212;&#8230;]]></description><link>https://katbusch.com/p/the-rules-of-optimization-why-so-many-performance-efforts-fail-cf06aad89099</link><guid isPermaLink="false">https://katbusch.com/p/the-rules-of-optimization-why-so-many-performance-efforts-fail-cf06aad89099</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Mon, 26 Sep 2016 13:32:31 GMT</pubDate><content:encoded><![CDATA[<blockquote><p>The First Rule of Program Optimization: Don&#8217;t do it. The Second Rule of Program Optimization (for experts only!): Don&#8217;t do it yet.&#8202;&#8212;&#8202;<em><a href="http://c2.com/cgi/wiki?RulesOfOptimization">Michael&nbsp;Jackson</a></em></p></blockquote><p>In our era of modern, speedy machines with oodles of memory, performance is something that few coders ever need to think about; but we think about it anyway.</p><p>We think about performance even when we don&#8217;t need to, and it&#8217;s much to our detriment. It complicates our lives and the lives of other coders in our codebases. Thus the first rule of <a href="https://hackernoon.com/tagged/optimization">optimization</a>: <strong>Don&#8217;t do it</strong>.</p><p>In this post, I&#8217;ll illustrate the reasoning behind the first and second rules of optimization, and I&#8217;ll provide some tips for the times when you really truly do need to undertake performance improvements.</p><p><em>Note: When we talk about performance we&#8217;re often referring to the speed a program is run, but the same rules apply to optimizing memory usage, battery usage, or whatever other resource might important to your program.</em></p><h3>Part I: The Bottleneck</h3><p>My first important real-world performance problem happened during a summer of research in college. A program for helping biologists identify genes was too slow. My advisor suggested that there was a part of the <a href="https://en.wikipedia.org/wiki/Graph_coloring">graph coloring</a> algorithm that took a long time, and I should start with optimizing that.</p><p>So I did. I had a great time squeezing CPU cycles out of C++. It&#8217;s a fun form of puzzle solving. I improved the performance of the graph coloring algorithm by 70%.</p><p>After a week of intricate optimization, I ran the whole program on the full dataset. It was only 15% faster&#8202;&#8212;&#8202;hardly noticeable. What was going on?</p><p>I dug around a little and discovered that when a process finished, it wrote long results to a file. The program caused many processes to write at once. As the operating system&#8217;s scheduler switched among the processes, the disk had to do tons of seeks to write each file. Disk seeks are expensive.</p><p>I added a few lines of code that created a file system lock so that only one process could write to disk at once. Sequential writes are much faster than seeks, so with that change the program was about 60% faster.</p><p>With a few lines of code, I&#8217;d more than cut the runtime in half. My week of C++ optimization had not. My advisor had pointed me in the C++ direction because he&#8217;d spent the most time on it. He&#8217;d probably only spent a couple minutes writing the code that writes to files.</p><p>I can&#8217;t tell you how many times in my years of performance work across many platforms I&#8217;ve seen people waste time on optimizations that do almost nothing. The most common failed performance effort is optimizing something that&#8217;s not the bottleneck. You often think you&#8217;re optimizing the bottleneck but you&#8217;re not looking at the whole picture.</p><p>Ironically it&#8217;s often the most experienced engineers that make this mistake first&nbsp;. It&#8217;s easy to get caught up in our own systems and forget there&#8217;s a whole other world out there. I once saw a group of senior backend engineers spend an entire hack week rewriting a complex web endpoint to use Go instead of Python. At the end, they found out that the bottleneck for the page was on the browser side of the app (and not the server side) making their performance improvement completely irrelevant.</p><p>These were experienced engineers who knew extremely well what made the server fast and slow, but their detailed knowledge of one part of the system prevented them from looking at the bigger picture.</p><p><strong>Always ask yourself, what performance does the end-user care about? How does your code impact that?</strong></p><h3>Part II: Complexity</h3><p>Performance improvements that don&#8217;t get at the bottleneck can actively harm your codebase. Almost all performance improvements increase the complexity of the code. Unneeded complexity creates a lot of problems that are not performance-related by making code harder to understand.</p><p>Let&#8217;s take a look at another example (slightly simplified) from a friend who used to use Python for graphics:</p><pre><code>for i in xrange(1000):
    my_mesh.draw()</code></pre><p>Imagine that before pushing this, the author noticed that draw() is being called 1000 times. Since the Python interpreter has to look up methods on each invocation, time could be saved by caching the reference to the draw method in a local variable:</p><pre><code>draw_method = my_mesh.draw
for i in xrange(1000):
    draw_method()</code></pre><p>This seems simple enough but it&#8217;s liable to cause a lot of problems down the line. While right now it might not seem like it will cause bugs, imagine that it&#8217;s in a large code base. Over time, many new lines of code might be added to the loop by many different engineers. Eventually draw_method might be far away from <em>draw_method = my_mesh.draw</em>, and the engineer will have to skip around and lose context to figure out what that means as they read through the code.</p><p>It will definitely slow people down and it could cause bugs. For instance, somebody might add live updating:</p><pre><code>draw_method = my_mesh.draw</code></pre><pre><code>for i in xrange(1000):
    &#8230;
    if my_mesh.outdated():
        my_mesh = updated_mesh
    &#8230;
    draw_method()</code></pre><p>Now there is a bug. The draw method will call draw on the first mesh, not the updated one, because the wrapper to my_mesh.draw wasn&#8217;t updated.</p><p>It&#8217;s quite common to see small performance &#8220;improvements&#8221; thrown in as part of larger changes, when those improvements haven&#8217;t been evaluated for how much they help. Even what seem like simple performance improvements add a cost. The cost is complexity. Complexity means bugs and more time reading, testing, and maintaining code. <a href="https://medium.com/@katbusch/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f#.rfc3wuwg1">Particularly in large or long-lived code bases, complexity is the biggest impediment to progress.</a></p><p>The change above is consistently faster on my <a href="https://hackernoon.com/tagged/machine">machine</a> by about&nbsp;.05ms on average. Is&nbsp;.05ms enough of an improvement to warrant the complexity? Most of the time, no.</p><p>You are simply not in a position to evaluate whether the complexity cost is worth the improvement in performance unless you have a big picture view of the performance of the system. Maybe you can optimize, but you certainly shouldn&#8217;t do it yet.</p><h3>Part III: How to approach performance problems</h3><p>But let&#8217;s say you think your system is really too slow, and you really do need you to improve its performance for empirical reasons. Here are few rules to get you started in a disciplined way.</p><h4>Get the right&nbsp;metric</h4><p>A lot of the problems I covered above come from measuring the wrong things. In my project in college, I measured the time for the graph algorithm to finish and not the time for the results to be written out. The hack week team was measuring server response time and not the time for the web page to appear to a user. <strong>Measuring the wrong thing leads to solving the wrong problem.</strong></p><p>Making sure you&#8217;re measuring the right number is the single most important thing to do when you&#8217;re tackling a performance problem. Without that, you have no idea how much you&#8217;re helping. Similarly, don&#8217;t necessarily trust those more experienced than you to tell you the bottleneck. Make sure you evaluate it yourself.</p><h4>How important are marginal gains in this&nbsp;system?</h4><p>Once you&#8217;re measuring the right thing, it will be a lot easier to tell which improvements are worthwhile. Nonetheless, it&#8217;s worth it to spend some time thinking about what your goals are.</p><p>You need to know not only how fast (or memory-intensive, etc) the system is, but also how much marginal gain you&#8217;ll get from improvements. Do you save your company money? Do you save your users time? If it&#8217;s a script that runs once a week that nobody is dependent on, even savings of an entire minute (basically forever in computer time) might not be worth adding complexity. But if it&#8217;s a function run a million times per second across a fleet of thousands of servers, savings of microseconds could save a lot of money.</p><p>If you understand what your performance goals are before beginning your work, you can make the right call on performance/complexity tradeoffs later on. If you&#8217;re being honest with yourself, you&#8217;ll often see that you should scrap marginal gains and focus on major wins.</p><h4>Measure right</h4><p>Lots of things can affect your performance measurement. You might be a mobile developer with a slick test device that has only one program running on it. If your goal is to produce a response in &lt; 10ms on average, that&#8217;ll be a lot easier on your test device than on your user&#8217;s four-year-old phone with low battery and a hundred apps running in the background.</p><p>It could also cause you to misunderstand your program&#8217;s characteristics. You might be I/O bound on a test device, when on most devices you&#8217;re actually CPU bound.</p><p>All sorts of external factors can affect performance including:</p><ul><li><p>Memory usage</p></li><li><p>Server load</p></li><li><p>Network bandwidth and latency</p></li><li><p>Battery level</p></li><li><p>CPU usage</p></li><li><p>Disk usage</p></li><li><p>Various caches at all layers</p></li></ul><p>When you&#8217;re measuring, try to make sure as many factors as possible are held constant so you can accurately compare different approaches. But also make an effort to understand what these factors usually look like, so you can make sure your appraisal of bottlenecks is realistic.</p><p>One simple strategy for cutting out the natural ebbs and flows of resources on a machine is to run your code many times. Python&#8217;s easy-to-use <a href="https://docs.python.org/2/library/timeit.html">timeit</a> library defaults to running your sample a million times. This can help average out some fluctuations in system resource availability.</p><h4>Try to integrate performance tests into your&nbsp;build</h4><p>Some software projects fail their builds if a commit causes a performance regression. If performance is important for your project, consider adding performance as part of your continuous integration. It can let you prevent performance regressions before they get shipped. The sooner you are aware of regressions, the easier they are to fix. In large codebases, small regressions can build up over time unless they&#8217;re aggressively kept in check. Tests can keep them out of your system.</p><h3>Finally, the optimization</h3><p>Armed with the right philosophy and information about your system, you&#8217;re ready to begin performance optimization. Don&#8217;t do it until you&#8217;ve profiled and analyzed and figured out a coherent strategy&#8202;&#8212;&#8202;then go forth and code!</p><p>Once you understand the basics of how to tackle general performance problems, you can start to delve deeper into your system. This is where things can get really fun and exciting. Small systems are elegant logic puzzles. Large systems are universes of slowness whose complex interlocking parts can take months to unravel. Improving performance in both environments can be beautiful.</p><p>It&#8217;s just not often the right thing to do.</p><p><em>For my guidance on web performance see <a href="https://katbusch.substack.com/p/so-your-website-is-slow-lets-fix-that-7c4e9e6a7131">So your website is slow? Let&#8217;s fix that</a>.</em></p>]]></content:encoded></item><item><title><![CDATA[Trust No One: An Introduction to Large Codebases for New Engineers]]></title><description><![CDATA[A series on best practices in large codebases for new engineers. This one focuses on how to think about large codebases via an introduction&#8230;]]></description><link>https://katbusch.com/p/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f</link><guid isPermaLink="false">https://katbusch.com/p/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f</guid><dc:creator><![CDATA[Kat Busch]]></dc:creator><pubDate>Fri, 22 Jul 2016 03:58:59 GMT</pubDate><content:encoded><![CDATA[<p><em>A series on best practices in large codebases for new engineers. This one focuses on how to think about large codebases via an introduction to the practice of commenting. Some variable names have been changed to protect the innocent.</em></p><p>The first thing I advise new software engineers fresh out of school is this: <strong>Trust no one</strong>.</p><p>It is certainly absurdly harsh, but I&#8217;ve found it serves a dual purpose: it gives engineers the confidence to share their own ideas (since existing assumptions are always suspect), and it helps motivate them to write robust, readable, and maintainable code.</p><p>Most engineers at the beginning of their career have coded primarily for themselves or their TAs or at most a small group. Much of that code is temporary and used only until you hand in your assignment or scrape and analyze your data.</p><p><strong>Habits that work very well for academic or personal code will bite you in the ass in a professional environment.</strong></p><p>Imagine an environment where dozens (maybe hundreds) are contributing alongside you. Your code will live on for years, and people will be trying to read it long after you&#8217;ve forgotten what you&#8217;ve written and maybe long after you&#8217;ve left the project or company.</p><p>In this <a href="https://hackernoon.com/tagged/codebase">codebase</a> things will break. Code that once worked will regress. It will regress in a thousand tiny and unpredictable ways. Your nice clean elegant API will have crap added to it, parameters slapped in and deprecated, and semantics inverted until it ends up like this real actual function from Windows XP used to create a symlink:</p><pre><code>BOOL br= ::DeviceIoControl(hDir, FSCTL_GET_REPARSE_POINT, NULL, 0,
  &amp;ReparseBuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &amp;dwRet, NULL);</code></pre><p>Source: <a href="http://www.flexhex.com/docs/articles/hard-links.phtml">http://www.flexhex.com/docs/articles/hard-links.phtml</a></p><h3>Part 1: Comments to the&nbsp;rescue?</h3><p>In this article, I&#8217;m going to discuss comments as a way to illustrate how code can evolve badly and how you can try to protect against future mistakes.</p><p>Often people turn to comments to combat the uncontrolled growth of codebases. But comments are ripe for abuse. As with all code, there are good ways and bad ways to write them.</p><h3>Comments can do terrible&nbsp;damage</h3><p>Let&#8217;s look at a practical example. Everybody knows it&#8217;s good to comment your code to &#8220;make it readable&#8221;. Some people also know it&#8217;s bad to comment your code &#8220;too much&#8221;.</p><p>Consider this comment:</p><pre><code>def user_history(days=90):
  # Do 90 days of history by default
  for i in xrange(days):
    &#8230;</code></pre><p>Let&#8217;s say you add this comment to your fresh new function. In a large codebase with many contributors, hundreds of people could end up reading this comment. They don&#8217;t learn anything new from reading it that they didn&#8217;t learn from the default parameter above it. That means it&#8217;s wasting engineers&#8217; time. But it&#8217;s not just a time waster.</p><p>Over the course of a year, hundreds of programmers will make changes to your codebase. Let&#8217;s say some new code is added:</p><pre><code>def user_history(days=90):
  prep_user_history_processing()
  do_some_more_stuff()
  &#8230;</code></pre><pre><code>  # Do 90 days of history by default
  for i in xrange(days):
    &#8230;</code></pre><p>There are now many lines of code separating the function&#8217;s declaration from your comment. They might not even fit on the screen together at the same time.</p><p>Now let&#8217;s say that in the intervening year, the user history processing has changed so that now we often read 1000 days of user history. Eventually one of the many programmers using this function is sick of writing user_history(days=1000). They go ahead and change the default.</p><pre><code>def user_history(days=1000):
  prep_user_history_processing()
  do_some_more_stuff()</code></pre><pre><code>  &#8230;</code></pre><pre><code>  # Do 90 days of history by default
  for i in xrange(days):
    &#8230;</code></pre><p>Boom. Tests pass. Code pushed.</p><p>A month later, another coder comes along and skims this function. They&#8217;re skipping through the file, and they see the comment but don&#8217;t actually look at the function declaration. They call the function with the default argument assuming it&#8217;s 90 days. Thus a bug is born.</p><p>This is how large code bases actually work. This is how many bugs are written.</p><h3>Learn to program defensively</h3><p>Comments that don&#8217;t add anything, like the one above, aren&#8217;t useful and are potentially dangerous. But more than that <strong>in a large codebase the evolution of your code is simply beyond your control</strong>. To write truly robust code you must ensure it&#8217;s not only correct (which itself can be challenging) but also resistant to skimming, misreading, carelessness, and growth.</p><p>When the author added the &#8220;90 days&#8221; comment above it was the second line of the function just below the default argument. They may have reasoned that if somebody were to change the number they would likely change the comment. They didn&#8217;t take into account that in the intervening year enough code could be added that the two would no longer even fit on the screen together at the same time.</p><p>It might seem lazy to you that someone would just skip to this part of the code without looking at the top of it. But coders are people and people make mistakes. Reading code is hard. <strong>With enough commits and enough time, mistakes are inevitable.</strong></p><p>To be a great programmer, you must take into account these future mistakes when you write your code today.</p><h3>Writing useful&nbsp;comments</h3><p>Thoughtful code that that keeps in mind and takes care of its many hapless readers can help turn the tide toward order in a large codebase. In our user history case, the coder can fix things by simply not having the comment in the first place. It&#8217;s not needed.</p><p>In the more general case the coder should consider for comments and for all code:</p><ol><li><p>How will this be useful over time?</p></li><li><p>What misinterpretations and misconfigurations will this cause?</p></li><li><p>Is this simple enough to be understood even in the face of a barrage of commits, our penchant for finding the easiest way, and our inexhaustible capacity to forget?</p></li><li><p>Who is this code for?</p></li></ol><p>To answer the last question it&#8217;s useful to distinguish whether a comment is for someone <em>calling</em> this function or for somebody <em>modifying</em> the function.</p><h3>Public comments</h3><p>If your function is public-facing in a large codebase then the caller shouldn&#8217;t have to read the function body to understand how to call it. A clear docstring should obviate the need to read (or likely misread) the code.</p><pre><code>def user_history(days=90):
  """Remotely fetch user events for the specified number of days.</code></pre><pre><code>  Args:
    days (int): the number of days to fetch for, rounded down by
        day to 12am</code></pre><pre><code>  Returns:
    list(UserEvent): all events found, empty list if none are found</code></pre><pre><code>  Raises:
    AccessException: raised if caller does not have permission
        to access this data
"""
  for i in xrange(days):
    &#8230;</code></pre><p>This comment has useful information upfront. The reader doesn&#8217;t have to trawl through many lines of python to figure out (possibly incorrectly) something like the return type. (<em>Note that static typing or something like Python&#8217;s <a href="https://docs.python.org/3/library/typing.html">type hints</a> can help with this.)</em></p><p>Take care with your docstring. Take time to consider whether it&#8217;s are really useful, whether and how it can be misinterpreted, and whether it&#8217;s likely to still be useful a month or a year from now, after many more commits.</p><p>The comment above is written in <a href="http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html">Google&#8217;s python args standard</a>. If your large codebase doesn&#8217;t have a standardized format for docstrings, you should invest time in starting one. <strong>Standards help protect against laziness and mistakes by providing a correct and easy habit.</strong></p><p>Because it&#8217;s the standard used by every function and it&#8217;s parsed for use on the docs page, everybody is accustomed to updating it. You could even deploy a linter that warns you if you&#8217;ve changed a function without updating its description.</p><p>Public comments do not address the implementation of your function. People only calling the function don&#8217;t need to know about that. That&#8217;s where internal comments come in.</p><h3>Internal comments</h3><p>In theory good code should speak for itself. Variables and functions should be clearly named and control flow should be obvious. But there will always be places when the code can&#8217;t speak for itself because of something very subtle or very complex.</p><p>Internal comments are written inline with the code and won&#8217;t be parsed to show in public docs. They&#8217;re intended for somebody intimate enough with the code that they&#8217;re actually going to go through and read it line by line. Well, let&#8217;s be honest; they&#8217;ll probably skip a lot of lines.</p><p>Here&#8217;s an example of an internal comment:</p><pre><code># DO NOT CHANGE THIS TO A SET!!! IT WILL BREAK!!1!!
number_list = [number for number in xrange(10)]
do_something(number_list)</code></pre><p>This comment is pretty useful&#8202;&#8212;&#8202;it says something that&#8217;s very hard to glean from the code, and if other coders read it then it could prevent bugs.</p><p>But as we know, <strong>many coders won&#8217;t read it</strong> because they&#8217;ll be skimming or they&#8217;ll make a mistake. This comment would be even more useful as an assert:</p><pre><code>number_list = [number for number in xrange(10)]
assert type(number_list) is list
do_something(number_list)</code></pre><p>Now we have the same information, but even if the coder is lazy and skims the comments, they still can&#8217;t cause a bug. Their testing will fail. (Yes, there might not be adequate testing. That&#8217;s a subject for <a href="https://hackernoon.com/treat-yourself-e55a7c522f71">another time</a>.)</p><p>Still, we didn&#8217;t explain why, and it&#8217;s really not obvious from reading the code why we have to have a list there. Somebody might come in and try to clean up the code by removing this seemingly superfluous assert. Here you can put in a useful comment to explain what&#8217;s going on.</p><pre><code>number_list = [number for number in xrange(10)]
# As of version 1.2.1, do_something has a bug that causes it to 
# crash if non-list collections are used for certain inputs.
assert type(number_list) is list
do_something(number_list)</code></pre><p>Now, we have a precise internal comment that</p><ul><li><p>tells the reader something useful that they can&#8217;t easily glean from the code</p></li><li><p>concerns the internals of the function that callers of this function don&#8217;t care about it</p></li><li><p>explains why it&#8217;s there and why you shouldn&#8217;t remove it</p></li></ul><p>This by no means covers all places you should use comments, but the general rules hold. Your code should be concise and explicit, and when it can&#8217;t easily speak for itself you can write a comment that tells the reader something useful. Often people won&#8217;t bother to read comments, so you shouldn&#8217;t rely on them too heavily.</p><h3>Compassion without&nbsp;trust</h3><p>The truth is that reading and understanding somebody else&#8217;s code is hard. Given enough people with enough code and enough time, mistakes and carelessness are guaranteed to happen regularly. Trust no one to be mistake-free.</p><p>When producing code for others you should keep these traits of your fellow coders (and yourself) in mind and defend against them as consistently as possible. By being thoughtful and concise and using well-understood standards, and by generally being aware of our own faults as we write code, we can begin to guard against our own mistakes. In future articles I&#8217;ll discuss how this applies to other aspects of software <a href="https://hackernoon.com/tagged/engineering">engineering</a> including testing and global state.</p><p>Article 1: <a href="https://katbusch.substack.com/p/trust-no-one-an-introduction-to-large-codebases-for-new-engineers-100586a87a5f">Trust No One: An Introduction to Large Codebases for New Engineers</a></p><p>Article 2: <a href="https://katbusch.substack.com/p/treat-yourself-e55a7c522f71">A Beginner&#8217;s Guide to Automated Testing</a></p><p>Article 3: <a href="https://katbusch.substack.com/p/quick-tips-for-gitting-on-a-team-33533d75aea9">Quick Tips for Gitting on a Team</a></p>]]></content:encoded></item></channel></rss>