<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="gaurangtandon.com/feed.xml" rel="self" type="application/atom+xml" /><link href="gaurangtandon.com/" rel="alternate" type="text/html" /><updated>2026-08-15T12:32:28+00:00</updated><id>gaurangtandon.com/feed.xml</id><title type="html">Gaurang Tandon</title><subtitle>I write about tech stuff.</subtitle><entry><title type="html">Make it easy for me to review your code</title><link href="gaurangtandon.com/blog/easy-code-review" rel="alternate" type="text/html" title="Make it easy for me to review your code" /><published>2024-04-15T02:30:00+00:00</published><updated>2024-04-15T02:30:00+00:00</updated><id>gaurangtandon.com/blog/easy-review</id><content type="html" xml:base="gaurangtandon.com/blog/easy-code-review"><![CDATA[<p>Almost every software engineer - irrespective of their tech stack - writes code collaboratively via Git. They submit their fixes or features as changesets. One of their colleagues then reviews the changeset and approves or rejects it. This reviewer must approve your changeset before it can be merged and deployed.</p>

<p>The reviewer’s job is tiring and thankless. They have to:</p>

<ol>
  <li>understand the context of your change, such as the issue at hand</li>
  <li>review your design to ensure that no other design is more feasible</li>
  <li>review the actual code to ensure it is a high-quality implementation</li>
  <li>test the implementation to ensure it actually works</li>
</ol>

<p>In this blog, I present specific and actionable suggestions, so you can make it easy for any reviewer to review your changeset. The <strong>mantra</strong> is to <strong>reduce the diff and make it understandable</strong>. All of the following suggestions directly implement this mantra.</p>

<h2 id="reduce-the-diff">Reduce the diff</h2>

<p><strong>tl;dr</strong>: Reviewing 5 separate changesets is easier than one giant bundled changeset because smaller, focused changes are easier to understand, test, and validate. It reduces cognitive load, improves visibility into changes, and allows for more targeted feedback and testing.</p>

<h3 id="do-not-make-unrelated-code-changes">Do not make unrelated code changes</h3>

<p>It is easy to accidentally bundle code formatting or code refactoring changes in your primary changeset. Note that any formatting or refactoring change should always be in its own separate changeset. Your primary changeset that implements a bugfix should never also be renaming multiple variables or changing indentations.</p>

<p>As a rule of thumb: <strong>you can separate changesets by their effect on the application’s function</strong>. Any code refactor or formatting will likely be a non-functional changeset. Any bugfix or feature is likely a functional changeset. You should keep these changesets separate.</p>

<p>In practice, this separation makes the reviewer’s task easy: <em>I know this changeset is only for code formatting, so any functional change is probably a mistake, and I should flag it.</em></p>

<h3 id="focus-each-changeset-on-one-specific-issue">Focus each changeset on one specific issue</h3>

<p>When working on one bugfix, it is easy to find more bugs on the same feature. The natural tendency is to bundle their bugfixes together in the changeset. After all, all the bugs are related to the same feature, and you fixed them around roughly the same time.</p>

<p>Practically, when all the bugfixes are bundled in one changeset, most code review tools (like in GitLab/GitHub) will present one large diff for all bugfixes together. This makes it difficult for the reviewer to understand which part of the diff corresponds to which bugfix.</p>

<p>You should put each bugfix in its own dedicated changeset, as this gives the reviewer (and you) the ability to reason about each bugfix independently.</p>

<p>As a rule of thumb, write a simple one-sentence summary of your changeset, and <strong>if the changeset does more than just the summary, it should be split up.</strong></p>

<h3 id="split-large-changesets-into-multiple-sequential-parts">Split large changesets into multiple sequential parts</h3>

<p>Changesets that implement large features, such as a new user walkthrough, are very difficult to review. That is because often these changesets implement the core feature and also re-organize a lot of the related code. For example: they extract helper functions from existing code.</p>

<p>It usually makes sense to split out these reorganization changes into a dedicated changeset. This makes the reviewer’s task easy, because 1. this is a non-functional changeset, and 2. it reduces the size of the primary changeset (which would now be just the core feature).</p>

<p>The rule of thumb I follow is: I first write the feature changeset fully. Once it is done, then I <strong>extract the non-functional changes into a separate changeset and send that first</strong>.</p>

<h2 id="make-the-changeset-understandable">Make the changeset understandable</h2>

<p><strong>tl;dr:</strong> Reviewing understandable changesets avoids needless asynchronous back-and-forth, and saves everyone time.</p>

<h3 id="explain-confusing-changes">Explain confusing changes</h3>

<p>You should always explain any change that is not immediately obvious. This saves the reviewer time from having to ask you a question, and you from having to respond to it. In asynchronous/remote teams, this saves everyone time.</p>

<p>As a <strong>rule of thumb</strong>, <strong>if I feel any part is confusing, I always explain it ahead of time</strong>, because the reviewer is likely to find it confusing too.</p>

<!-- ### Add in-code comments for explanations

The discussions on changesets may not persist in time. You might move platforms, prune older changesets, etc.

Generally, try to add explanations to the code itself, or to the commit messages. These explanations not only benefit the reviewer but also benefit the future code reader, who will also be likely looking for help. -->

<h3 id="explain-alternative-solutions-for-the-same-issue">Explain alternative solutions for the same issue</h3>

<p>The reviewer is also a subject matter expert just like you. They’re likely to think of other solutions to the same issue. If you have also thought about them, you should make that clear in the changeset. Specifically: which other solutions did you implement, and why did they fall short of the proposed solution?</p>

<p>Without your explanation, the reviewer will just be confused: <em>Why didn’t we implement solution X which appears much simpler?</em></p>

<h3 id="add-tests">Add tests</h3>

<p>Tests help the reviewer understand how a given bug manifests in practice. Tests also give the reviewer confidence that your implementation does fix that bug. Finally, tests clarify your thought process, because they describe all the instances that you consider relevant to the bug. This allows the reviewer to suggest any new instance which should also be tested in this changeset.</p>

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

<p>I hope you found the blog post useful. Feel free to reach out to me to continue the discussion.</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[Almost every software engineer - irrespective of their tech stack - writes code collaboratively via Git. They submit their fixes or features as changesets. One of their colleagues then reviews the changeset and approves or rejects it. This reviewer must approve your changeset before it can be merged and deployed.]]></summary></entry><entry><title type="html">Email is a great note-taking tool for debugging sessions</title><link href="gaurangtandon.com/blog/note-taking-email" rel="alternate" type="text/html" title="Email is a great note-taking tool for debugging sessions" /><published>2024-01-01T04:30:00+00:00</published><updated>2024-01-01T04:30:00+00:00</updated><id>gaurangtandon.com/blog/note-taking-debugging</id><content type="html" xml:base="gaurangtandon.com/blog/note-taking-email"><![CDATA[<p>In my everyday job, I often have to reverse-engineer parts of a web app (like Google Docs) using its minified JavaScript files. These files have many levels of delegation, have timer-oriented code, and have weird data transformations. It is difficult to complete the reverse engineering in just one session. So, I must keep taking notes to remember all the tiny details and map out my progress.</p>

<p>If you have also been stuck in a similar debugging session, you would agree on the importance of note-taking. It helps you to track two primary parameters:</p>

<ul>
  <li>what information is certainly known so far,</li>
  <li>what next steps we can take to extract more information</li>
</ul>

<p>Now, the note-taking tool is insignificant compared to the skill in identifying those two parameters. But, the correct choice can still help you stay focused on the task at hand.</p>

<p>Back in the day, I wrote my debugging notes in Google Docs. Google Docs is great if you are working live with another team member. It is also great for writing colorful, “rich” debugging notes. If you prefer that kind of style, by all means, continue using it!</p>

<p>In this blog post, I will instead share an alternative: good old email. For solo debugging sessions, I have found email to be much better than Google Docs.</p>

<h2 id="how-i-use-email-for-note-taking-while-debugging">How I use email for note-taking while debugging</h2>

<p>Each debugging session belongs to one very long email thread, that is sent to my own email address. While debugging, I write my latest observations in the latest email draft. When I feel confident about these observations, I send the email (to myself), and then start the next draft in that same email thread. I repeat this until I find all the info that I need.</p>

<h2 id="why-i-prefer-email-over-google-docs">Why I prefer email over Google Docs</h2>

<h3 id="commiting-notes-helps-enforce-progress-over-time">“Commit”ing notes helps enforce progress over time</h3>

<p>Writing notes in an email thread helps the debugging session to “move forward in time”. You cannot edit any past notes, as they have been “committed” to the email thread - like code is committed to a repository. You can only ever update the latest email draft: this restriction helps you focus on debugging. Every new email you send counts as a new stepping stone, getting you closer towards the final solution.</p>

<p>In Google Docs, there is no perspective of how notes were taken over time. Sure, you can try to enforce this visually, by writing the notes sequentially. In practice, this is hard to enforce. It is easy to start editing the older notes in the Google Doc while debugging, which can lead to wasted effort. More on this in the next section.</p>

<h3 id="focusing-on-the-problem-rather-than-the-formatting">Focusing on the problem rather than the formatting</h3>

<p>While in a debugging session, it is easy to get lost trying to make the notes in Google Docs “look good”. This might include:</p>

<ol>
  <li>cleaning up older notes in the light of latest observations,</li>
  <li>putting bold or italics on certain phrases to draw attention to them</li>
  <li>cleaning up the language and structure of the past sentences</li>
</ol>

<p>None of this is useful when you’re already inside the debugging session. This distracts you from the core task of thinking about the potential solutions for the problem.</p>

<p>Email instead supports limited formatting, and it also prevents you from editing previously sent emails. This reduced area for exploration of the notes, forces you to explore the problem more.</p>

<p>In other words, the developer’s time is the most valuable and it is also the most limited. More time spent formatting the notes necessarily implies less time spent debugging the actual issue.</p>

<h3 id="email-separates-ongoing-debugging-research-from-a-postmortem-report">Email separates ongoing debugging research from a postmortem report</h3>

<p>When writing notes in Google Docs, the writer might want to start tidying them up to be ready for the final presentation in the team meeting. Intuitively, this makes sense: by putting in an incremental effort to prepare the presentation, you lighten the extra workload when the debugging session ends.</p>

<p>Although intuitive, this is counter-productive. You cannot know in advance if the current chain of thought is in the right direction. You might spend time fleshing out specific thoughts, only to realize later they don’t matter in the final solution. Moreover, this again distracts you from the core task of debugging.</p>

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

<p>While debugging an issue, the primary aim for the engineer is to find a simple and reliable solution as soon as possible. While debugging, the engineer usually has a long train of thoughts, which should be written down, to help solidify those thoughts. A restrictive tool like email helps force the engineer to focus on debugging; whereas a free-form tool like Docs encourages exploration beyond what is necessary for the primary aim.</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[In my everyday job, I often have to reverse-engineer parts of a web app (like Google Docs) using its minified JavaScript files. These files have many levels of delegation, have timer-oriented code, and have weird data transformations. It is difficult to complete the reverse engineering in just one session. So, I must keep taking notes to remember all the tiny details and map out my progress.]]></summary></entry><entry><title type="html">Chrome extension difficulty: MV2 vs MV3 comparison</title><link href="gaurangtandon.com/blog/chrome-extension-mv2-mv3" rel="alternate" type="text/html" title="Chrome extension difficulty: MV2 vs MV3 comparison" /><published>2023-12-09T18:30:00+00:00</published><updated>2023-12-09T18:30:00+00:00</updated><id>gaurangtandon.com/blog/chrome-extension-barrier</id><content type="html" xml:base="gaurangtandon.com/blog/chrome-extension-mv2-mv3"><![CDATA[<p>Chrome extensions are required to migrate to Manifest V3 by June 2024<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. Many new features have been added till Chrome 120 to support this migration<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. In this post, we will compare the implementation difficulty of four example use cases in Manifest V2 and in Manifest V3.</p>

<h2 id="introduction">Introduction</h2>

<p>Chrome extensions enable developers to extend the web platform with custom styling and/or custom interactivity <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>. This implies that Chrome extension developers are usually Web Developers, as they can write the necessary JavaScript, CSS, and HTML. When the Chrome extension platform uses the basic Web APIs<sup id="fnref:apis" role="doc-noteref"><a href="#fn:apis" class="footnote" rel="footnote">4</a></sup>, then the existing Web Developers can easily contribute to the Chrome extension platform.</p>

<!--This is a net win for all participants: 

- web developers can quickly publish extensions to reach new users and monetize their work,
- users can access vast numbers of extensions to customize their browsing experience,
- Chrome can make a commission on the extension sales (although this was discontinued a few years ago) [^4]

The further the Chrome extensions platform diverges from Web fundamentals, the higher it raises the barrier to entry for Web Developers.-->

<h2 id="manifest-v2">Manifest V2</h2>

<p>There are only two key components in the Manifest V2 platform: a background page and a content script. The background page:</p>

<ul>
  <li>is a single instance that runs in the <em>background</em> of the Chrome browser. It starts when the Chrome browser starts, and is destroyed only when the Chrome process exits.</li>
  <li>is a <em>page</em> with DOM API access</li>
  <li>is a high-privilege context: here, you can store user data, run business logic, and access all <code class="language-plaintext highlighter-rouge">chrome.*</code> APIs.</li>
</ul>

<p>On the other hand, the content script:</p>

<ul>
  <li>has access to the target web page <em>content</em> (such as the DOM on youtube.com) and can manipulate it,</li>
  <li>is a short-lived <em>script</em>, so it dies out when that Chrome tab is closed.</li>
  <li>is a low-privilege context: mostly executes instructions that it receives from the background page via messaging.</li>
</ul>

<h3 id="four-example-use-cases-with-the-background-page">Four example use cases with the background page</h3>

<p>Using the Manifest V2 background page, let us see how to implement the following four example use cases:</p>

<h4 id="handling-global-session-state">Handling global session state</h4>

<p>To create global state per new browser session, we can put a global variable at the top of our JavaScript file, and read/write to it, like so:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// at the start of our file</span>
<span class="kd">let</span> <span class="nx">isUserLoggedIn</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>

<span class="c1">// ...read the state anywhere</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">isUserLoggedIn</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* do something */</span> <span class="p">}</span>
<span class="k">else</span> <span class="p">{</span> <span class="cm">/* do something else */</span> <span class="p">}</span>

<span class="c1">// ...update the state from anywhere</span>
<span class="nx">onAuthStateChanged</span><span class="p">((</span><span class="nx">user</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span> <span class="nx">isUserLoggedIn</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="p">}</span>
  <span class="k">else</span> <span class="p">{</span> <span class="nx">isUserLoggedIn</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<h4 id="using-setinterval-or-settimeout">Using setInterval or setTimeout</h4>

<p>This is easy to do in a background page:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">interval</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// my logic here</span>
<span class="p">},</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span> <span class="c1">// runs once a minute</span>

<span class="c1">// ...if later needed to clear</span>
<span class="nx">clearInterval</span><span class="p">(</span><span class="nx">interval</span><span class="p">);</span>
</code></pre></div></div>

<p>Note that timers in background pages are subject to timer throttling, like any backgrounded tab. This doesn’t affect intervals for a minute or higher.</p>

<h4 id="dom-api-access">DOM API access</h4>

<p>The background page can access any DOM API, like so:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ...read/write persisted storage</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="dl">'</span><span class="s1">key</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">value</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="dl">'</span><span class="s1">key</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// ...manipulate the user clipboard</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">execCommand</span><span class="p">(</span><span class="dl">'</span><span class="s1">paste</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>

<span class="c1">// ...play audio</span>
<span class="k">new</span> <span class="nx">Audio</span><span class="p">(</span><span class="dl">'</span><span class="s1">file.mp3</span><span class="dl">'</span><span class="p">).</span><span class="nx">play</span><span class="p">();</span>

<span class="c1">// ...check the user OS</span>
<span class="nb">navigator</span><span class="p">.</span><span class="nx">appVersion</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">Win</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>

<p>Note that these are perhaps the most basic Web APIs. Imagine your first <code class="language-plaintext highlighter-rouge">index.js</code> and <code class="language-plaintext highlighter-rouge">index.html</code> project: it is likely you used one or more of these APIs.</p>

<h4 id="multi-process-synchronization">Multi-process synchronization</h4>

<p>There is only one JavaScript process - the background page. It can manage the entire extension, and run all the tasks on its own. Therefore, there is no synchronization overhead between multiple processes.</p>

<!--More importantly, we can **import any code snippet originally written for the Web platform**, and easily create a Chrome extension [!ref](cover exceptions) with that code. -->

<p>Overall, we observe that Manifest V2 supports the most basic Web API features in a consistent manner, enabling web developers to get started easily.</p>

<h2 id="manifest-v3">Manifest V3</h2>

<p>Manifest V3 splits the background page into two separate processes: a service worker and an offscreen document.</p>

<p>The <strong>extension service worker</strong> is slightly similar to the Web API’s service worker. In particular, the extension service worker<sup id="fnref:sw" role="doc-noteref"><a href="#fn:sw" class="footnote" rel="footnote">5</a></sup>:</p>

<ul>
  <li>is a single instance process, that is responsible for managing the entire extension</li>
  <li>cannot access any DOM API</li>
  <li>is loaded only on demand - for example, when responding to an event</li>
  <li>is unloaded after 30 seconds (though some exceptions are available<sup id="fnref:lifetime" role="doc-noteref"><a href="#fn:lifetime" class="footnote" rel="footnote">6</a></sup>) of inactivity</li>
</ul>

<p>The <strong>offscreen document</strong> is identical to the background page - except it cannot access <code class="language-plaintext highlighter-rouge">chrome.*</code> APIs <sup id="fnref:offscreen" role="doc-noteref"><a href="#fn:offscreen" class="footnote" rel="footnote">7</a></sup>.</p>

<p>The extension service worker can perform many tasks on its own. However, when it needs to play audio, access the clipboard, run DOM operations, etc. (an in-exhaustive list is available<sup id="fnref:list" role="doc-noteref"><a href="#fn:list" class="footnote" rel="footnote">8</a></sup>) - then it should message the offscreen document to perform that task. The offscreen document will then send a response back to the service worker.</p>

<p>Note that, in Manifest V2, the background page itself can manage all tasks on its own. However, in MV3, we need to setup a messaging channel between the extension service worker and the offscreen document to perform a few of these tasks.</p>

<h3 id="four-example-use-cases-with-the-extension-service-workers">Four example use cases with the extension service workers:</h3>

<p>Let us now again see how to implement the same four example use cases, using an extension service worker this time:</p>

<!--This setup complicates Chrome extension development. In the following sections, I demonstrate how the new "recommended" approach is neither ergonomic nor familiar to existing web developers.-->

<!--### Service worker as an additional concept

The service worker doesn't have access to any DOM APIs and it can be killed and be restarted by the browser on certain events. This distinction can potentially trip up many Web Developers who just want to create a simple Chrome extension. The term "service worker" itself is not common among beginner camps. <!--Ideally, this is an important concept that web developers should have practice with. Realistically though, web developers spend most of their time writing JavaScript in the frontend DOM context, or in the backend NodeJS context, but very rarely in the Service Worker context. -->

<h4 id="handling-global-session-state-1">Handling global session state</h4>

<p>As per the docs, we need to design our service worker to be “resilient against unexpected termination” <sup id="fnref:lifetime:1" role="doc-noteref"><a href="#fn:lifetime" class="footnote" rel="footnote">6</a></sup>. This effectively prohibits storing global state in-memory.</p>

<p>Now, we need to use <code class="language-plaintext highlighter-rouge">chrome.storage</code> APIs, which are async and verbose:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ...writing a key</span>
<span class="nx">chrome</span><span class="p">.</span><span class="nx">storage</span><span class="p">.</span><span class="nx">local</span><span class="p">.</span><span class="kd">set</span><span class="p">({</span> <span class="na">key</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}).</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Value was set</span><span class="dl">'</span><span class="p">);</span>

  <span class="c1">// ...reading a key</span>
  <span class="nx">chrome</span><span class="p">.</span><span class="nx">storage</span><span class="p">.</span><span class="nx">local</span><span class="p">.</span><span class="kd">get</span><span class="p">([</span><span class="dl">"</span><span class="s2">key</span><span class="dl">"</span><span class="p">]).</span><span class="nx">then</span><span class="p">((</span><span class="nx">result</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Value currently is </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">result</span><span class="p">.</span><span class="nx">key</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<h4 id="using-setinterval-or-settimeout-1">Using setInterval or setTimeout</h4>

<p>Any <code class="language-plaintext highlighter-rouge">setTimeout</code> or <code class="language-plaintext highlighter-rouge">setInterval</code> that is longer than thirty seconds can unexpectedly terminate. Now, we need to use the <code class="language-plaintext highlighter-rouge">chrome.alarms</code> API. To create the alarm, we run code like so <sup id="fnref:alarms" role="doc-noteref"><a href="#fn:alarms" class="footnote" rel="footnote">9</a></sup>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">createAlarm</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">alarm</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">chrome</span><span class="p">.</span><span class="nx">alarms</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">my-alarm</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">alarm</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">chrome</span><span class="p">.</span><span class="nx">alarms</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span> <span class="na">periodInMinutes</span><span class="p">:</span> <span class="mi">1</span> <span class="p">});</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">createAlarm</span><span class="p">();</span>
</code></pre></div></div>

<p>and then to handle when it is triggered:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">chrome</span><span class="p">.</span><span class="nx">alarms</span><span class="p">.</span><span class="nx">onAlarm</span><span class="p">.</span><span class="nx">addListener</span><span class="p">((</span><span class="nx">alarm</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">alarm</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">my-alarm</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// ...my logic here</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<h4 id="dom-api-access-1">DOM API access</h4>

<p>Any code that uses DOM APIs needs to run in the offscreen document. This introduces extra complexity, such as:</p>

<ol>
  <li>handling the creation of offscreen document<sup id="fnref:lifecycle" role="doc-noteref"><a href="#fn:lifecycle" class="footnote" rel="footnote">10</a></sup></li>
  <li>asynchronous message passing with the offscreen document. This entails that all functions in the call stack also need to be async.</li>
  <li>ensuring the message to and the response from the offscreen document both are JSON serializable.</li>
</ol>

<h4 id="multi-process-synchronization-1">Multi-process synchronization</h4>

<p>Now we need to split our business logic into two files that run in two separate processes. This introduces extra complexity, such as:</p>

<ul>
  <li>What happens if the offscreen document runs into an async exception while responding to the service worker? <sup id="fnref:message" role="doc-noteref"><a href="#fn:message" class="footnote" rel="footnote">11</a></sup> Will the service worker then be unloaded after waiting over 30 seconds for a response?</li>
  <li>The code editor loses type-checking information on the data sent with the message, and the response received for the message.</li>
  <li>If we have multiple files being imported across the two files, we need to ensure that no code is unintentionally shared between the two processes. Otherwise, we can accidentally inflate our app’s bundle size.</li>
</ul>

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

<p>This blog post is only a factual and in-exhaustive summary of changes from Manifest V2 to Manifest V3 for four specific use cases. The full reasoning behind the Manifest V3 migration is much more complex and out of scope for this blog post. The example use cases I have taken may not be relevant for your project. In other use cases, it may be possible that MV3 has improved the developer experience.</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://developer.chrome.com/blog/resuming-the-transition-to-mv3">https://developer.chrome.com/blog/resuming-the-transition-to-mv3</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p><a href="https://developer.chrome.com/blog/chrome-120-beta-whats-new-for-extensions">https://developer.chrome.com/blog/chrome-120-beta-whats-new-for-extensions</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p><a href="https://developer.chrome.com/docs/extensions/develop">https://developer.chrome.com/docs/extensions/develop</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:apis" role="doc-endnote">
      <p><a href="https://developer.mozilla.org/en-US/docs/Web/API">https://developer.mozilla.org/en-US/docs/Web/API</a> <a href="#fnref:apis" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:sw" role="doc-endnote">
      <p><a href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers">https://developer.chrome.com/docs/extensions/develop/concepts/service-workers</a> <a href="#fnref:sw" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:lifetime" role="doc-endnote">
      <p><a href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle#idle-shutdown">https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle#idle-shutdown</a> <a href="#fnref:lifetime" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:lifetime:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:offscreen" role="doc-endnote">
      <p><a href="https://developer.chrome.com/docs/extensions/reference/api/offscreen">https://developer.chrome.com/docs/extensions/reference/api/offscreen</a> <a href="#fnref:offscreen" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:list" role="doc-endnote">
      <p><a href="https://developer.chrome.com/docs/extensions/reference/api/offscreen#type-Reason">https://developer.chrome.com/docs/extensions/reference/api/offscreen#type-Reason</a> <a href="#fnref:list" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:alarms" role="doc-endnote">
      <p><a href="https://github.com/GoogleChrome/webstore-docs/blob/master/money.html#L57">https://github.com/GoogleChrome/webstore-docs/blob/master/money.html#L57</a> <a href="#fnref:alarms" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:lifecycle" role="doc-endnote">
      <p><a href="https://developer.chrome.com/docs/extensions/reference/api/offscreen#maintain_the_lifecycle_of_an_offscreen_document">https://developer.chrome.com/docs/extensions/reference/api/offscreen#maintain_the_lifecycle_of_an_offscreen_document</a> <a href="#fnref:lifecycle" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:message" role="doc-endnote">
      <p>this is in the case of an asynchronous messaging channel, which remains open until a response is received <a href="#fnref:message" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[Chrome extensions are required to migrate to Manifest V3 by June 20241. Many new features have been added till Chrome 120 to support this migration2. In this post, we will compare the implementation difficulty of four example use cases in Manifest V2 and in Manifest V3. https://developer.chrome.com/blog/resuming-the-transition-to-mv3 &#8617; https://developer.chrome.com/blog/chrome-120-beta-whats-new-for-extensions &#8617;]]></summary></entry><entry><title type="html">How to rewrite classes using closures in JavaScript</title><link href="gaurangtandon.com/blog/javascript-class-closure" rel="alternate" type="text/html" title="How to rewrite classes using closures in JavaScript" /><published>2023-10-21T18:30:00+00:00</published><updated>2023-10-21T18:30:00+00:00</updated><id>gaurangtandon.com/blog/javascript-class</id><content type="html" xml:base="gaurangtandon.com/blog/javascript-class-closure"><![CDATA[<h2 id="update">Update</h2>

<p>I’m very humbled by the insightful discussion on <a href="https://news.ycombinator.com/item?id=37966510">HackerNews</a>. Really, thank you! I have now updated the blog post to address the comments. You can always view the diff <a href="https://github.com/GaurangTandon/gaurangtandon.github.io/commits/master/_posts/2023-10-22-javascript-class.markdown">on GitHub</a>.</p>

<h2 id="introduction">Introduction</h2>

<p>As much as I dislike the <code class="language-plaintext highlighter-rouge">class</code> syntax in JavaScript, it used to be my default choice when I needed an object factory, with support for static and instance level properties.</p>

<p>I recently found a clean way to eliminate the <code class="language-plaintext highlighter-rouge">class</code> syntax while still maintaining these properties, and in this post, I’ll show you how.</p>

<h2 id="classy-issues"><em>Classy</em> issues</h2>

<p>Classes are plagued with issues, such as:</p>

<h3 id="this-is-awkward"><code class="language-plaintext highlighter-rouge">this</code> is awkward</h3>

<p>Having to write:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="p">.</span><span class="nx">progressBar</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">handler</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
</code></pre></div></div>

<p>is much worse compared to:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">progressBar</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="nx">handler</span><span class="p">);</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">this</code> prefix is mandatory for every instance property, which increases code bloat. When passing methods around, you have to carefully rebind them to the correct object.</p>

<h3 id="regarding-private-properties">Regarding private properties</h3>

<p>I initially wrote that classes don’t support private properties. That was completely wrong, as <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields">this MDN article mentions</a>.
Using private properties helps better encapsulate internal logic, that we don’t want external code to depend on. I’m glad JS classes support that.</p>

<h3 id="regarding-readonly-properties">Regarding readonly properties</h3>

<p>I initially wrote that we cannot have instance properties that are public but readonly. For example, this is valid JavaScript:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">MyClass</span> <span class="p">{</span>
  <span class="kd">static</span> <span class="nx">prop</span> <span class="o">=</span> <span class="mi">123</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">MyClass</span><span class="p">.</span><span class="nx">prop</span> <span class="o">=</span> <span class="mi">456</span><span class="p">;</span>
</code></pre></div></div>

<p>As comments pointed out, you can define a <code class="language-plaintext highlighter-rouge">get</code>ter instead:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">MyClass</span> <span class="p">{</span>
  <span class="kd">static</span> <span class="kd">get</span> <span class="nx">prop</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="mi">123</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="nx">MyClass</span><span class="p">.</span><span class="nx">prop</span> <span class="o">=</span> <span class="mi">456</span><span class="p">;</span> <span class="c1">// doesn't change the value</span>
</code></pre></div></div>

<h3 id="poor-bundler-optimization">Poor bundler optimization</h3>

<p>If you are building a large class encapsulating some complex logic, you likely have dozens of private methods as “helper” methods, but only a few externally exposed methods. Unfortunately, build tools like <code class="language-plaintext highlighter-rouge">terser</code> or <code class="language-plaintext highlighter-rouge">webpack</code> cannot optimize them well. For example:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">MyClass</span> <span class="p">{</span>
  <span class="kd">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">prop</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nx">publicMethod</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">property</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">unused</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">instance</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MyClass</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">instance</span><span class="p">.</span><span class="nx">prop</span><span class="p">);</span>
</code></pre></div></div>

<p>When optimized with <code class="language-plaintext highlighter-rouge">npx webpack --mode production</code>, this results in:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(()</span><span class="o">=&gt;</span><span class="p">{</span><span class="kd">const</span> <span class="nx">o</span><span class="o">=</span><span class="k">new</span> <span class="kd">class</span><span class="p">{</span><span class="kd">constructor</span><span class="p">(){</span><span class="k">this</span><span class="p">.</span><span class="nx">prop</span><span class="o">=</span><span class="mi">5</span><span class="p">}</span><span class="nx">publicMethod</span><span class="p">(){</span><span class="k">this</span><span class="p">.</span><span class="nx">property</span><span class="o">=</span><span class="mi">6</span><span class="p">}};</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">o</span><span class="p">.</span><span class="nx">prop</span><span class="p">)})();</span>
</code></pre></div></div>

<p>Notice how: 1. the unused instance method is not removed, and 2. the name of this instance method is not simplified.</p>

<h3 id="poor-linting-experience">Poor linting experience</h3>

<p>Unused methods and properties are never flagged by <code class="language-plaintext highlighter-rouge">eslint</code> (here’s an <a href="https://eslint.org/play/#eyJ0ZXh0IjoiY29uc3QgdW51c2VkVmFyID0gMTIzO1xuXG5jbGFzcyBNeUNsYXNzIHtcbiAgc3RhdGljIHVudXNlZFN0YXRpY1Byb3BlcnR5ID0gNjtcbiAgXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMudW51c2VkUHJvcGVydHkgPSA1O1xuICB9XG4gIFxuICB1bnVzZWRNZXRob2QoKSB7XG4gICAgXG4gIH1cbn1cblxubmV3IE15Q2xhc3MoKTtcbiIsIm9wdGlvbnMiOnsiZW52Ijp7ImVzNiI6dHJ1ZX0sInBhcnNlck9wdGlvbnMiOnsiZWNtYUZlYXR1cmVzIjp7fSwiZWNtYVZlcnNpb24iOiJsYXRlc3QiLCJzb3VyY2VUeXBlIjoic2NyaXB0In0sInJ1bGVzIjp7ImNvbnN0cnVjdG9yLXN1cGVyIjpbImVycm9yIl0sImZvci1kaXJlY3Rpb24iOlsiZXJyb3IiXSwiZ2V0dGVyLXJldHVybiI6WyJlcnJvciJdLCJuby1hc3luYy1wcm9taXNlLWV4ZWN1dG9yIjpbImVycm9yIl0sIm5vLWNhc2UtZGVjbGFyYXRpb25zIjpbImVycm9yIl0sIm5vLWNsYXNzLWFzc2lnbiI6WyJlcnJvciJdLCJuby1jb21wYXJlLW5lZy16ZXJvIjpbImVycm9yIl0sIm5vLWNvbmQtYXNzaWduIjpbImVycm9yIl0sIm5vLWNvbnN0LWFzc2lnbiI6WyJlcnJvciJdLCJuby1jb25zdGFudC1jb25kaXRpb24iOlsiZXJyb3IiXSwibm8tY29udHJvbC1yZWdleCI6WyJlcnJvciJdLCJuby1kZWJ1Z2dlciI6WyJlcnJvciJdLCJuby1kZWxldGUtdmFyIjpbImVycm9yIl0sIm5vLWR1cGUtYXJncyI6WyJlcnJvciJdLCJuby1kdXBlLWNsYXNzLW1lbWJlcnMiOlsiZXJyb3IiXSwibm8tZHVwZS1lbHNlLWlmIjpbImVycm9yIl0sIm5vLWR1cGUta2V5cyI6WyJlcnJvciJdLCJuby1kdXBsaWNhdGUtY2FzZSI6WyJlcnJvciJdLCJuby1lbXB0eSI6WyJlcnJvciJdLCJuby1lbXB0eS1jaGFyYWN0ZXItY2xhc3MiOlsiZXJyb3IiXSwibm8tZW1wdHktcGF0dGVybiI6WyJlcnJvciJdLCJuby1leC1hc3NpZ24iOlsiZXJyb3IiXSwibm8tZXh0cmEtYm9vbGVhbi1jYXN0IjpbImVycm9yIl0sIm5vLWV4dHJhLXNlbWkiOlsiZXJyb3IiXSwibm8tZmFsbHRocm91Z2giOlsiZXJyb3IiXSwibm8tZnVuYy1hc3NpZ24iOlsiZXJyb3IiXSwibm8tZ2xvYmFsLWFzc2lnbiI6WyJlcnJvciJdLCJuby1pbXBvcnQtYXNzaWduIjpbImVycm9yIl0sIm5vLWlubmVyLWRlY2xhcmF0aW9ucyI6WyJlcnJvciJdLCJuby1pbnZhbGlkLXJlZ2V4cCI6WyJlcnJvciJdLCJuby1pcnJlZ3VsYXItd2hpdGVzcGFjZSI6WyJlcnJvciJdLCJuby1sb3NzLW9mLXByZWNpc2lvbiI6WyJlcnJvciJdLCJuby1taXNsZWFkaW5nLWNoYXJhY3Rlci1jbGFzcyI6WyJlcnJvciJdLCJuby1taXhlZC1zcGFjZXMtYW5kLXRhYnMiOlsiZXJyb3IiXSwibm8tbmV3LXN5bWJvbCI6WyJlcnJvciJdLCJuby1ub25vY3RhbC1kZWNpbWFsLWVzY2FwZSI6WyJlcnJvciJdLCJuby1vYmotY2FsbHMiOlsiZXJyb3IiXSwibm8tb2N0YWwiOlsiZXJyb3IiXSwibm8tcHJvdG90eXBlLWJ1aWx0aW5zIjpbImVycm9yIl0sIm5vLXJlZGVjbGFyZSI6WyJlcnJvciJdLCJuby1yZWdleC1zcGFjZXMiOlsiZXJyb3IiXSwibm8tc2VsZi1hc3NpZ24iOlsiZXJyb3IiXSwibm8tc2V0dGVyLXJldHVybiI6WyJlcnJvciJdLCJuby1zaGFkb3ctcmVzdHJpY3RlZC1uYW1lcyI6WyJlcnJvciJdLCJuby1zcGFyc2UtYXJyYXlzIjpbImVycm9yIl0sIm5vLXRoaXMtYmVmb3JlLXN1cGVyIjpbImVycm9yIl0sIm5vLXVuZGVmIjpbImVycm9yIl0sIm5vLXVuZXhwZWN0ZWQtbXVsdGlsaW5lIjpbImVycm9yIl0sIm5vLXVucmVhY2hhYmxlIjpbImVycm9yIl0sIm5vLXVuc2FmZS1maW5hbGx5IjpbImVycm9yIl0sIm5vLXVuc2FmZS1uZWdhdGlvbiI6WyJlcnJvciJdLCJuby11bnNhZmUtb3B0aW9uYWwtY2hhaW5pbmciOlsiZXJyb3IiXSwibm8tdW51c2VkLWxhYmVscyI6WyJlcnJvciJdLCJuby11bnVzZWQtdmFycyI6WyJlcnJvciJdLCJuby11c2VsZXNzLWJhY2tyZWZlcmVuY2UiOlsiZXJyb3IiXSwibm8tdXNlbGVzcy1jYXRjaCI6WyJlcnJvciJdLCJuby11c2VsZXNzLWVzY2FwZSI6WyJlcnJvciJdLCJuby13aXRoIjpbImVycm9yIl0sInJlcXVpcmUteWllbGQiOlsiZXJyb3IiXSwidXNlLWlzbmFuIjpbImVycm9yIl0sInZhbGlkLXR5cGVvZiI6WyJlcnJvciJdLCJuby11bnVzZWQtcHJpdmF0ZS1jbGFzcy1tZW1iZXJzIjpbImVycm9yIl19fX0=">example</a> class). This makes it difficult to refactor existing code.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// flagged</span>
<span class="kd">const</span> <span class="nx">unusedVar</span> <span class="o">=</span> <span class="mi">123</span><span class="p">;</span>

<span class="kd">class</span> <span class="nx">MyClass</span> <span class="p">{</span>
  <span class="c1">// not flagged</span>
  <span class="kd">static</span> <span class="nx">unusedStaticProperty</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// not flagged</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">unusedProperty</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
  <span class="p">}</span>
  
  <span class="c1">// not flagged</span>
  <span class="nx">unusedMethod</span><span class="p">()</span> <span class="p">{</span>
    
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">new</span> <span class="nx">MyClass</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="not-hoisted-up">Not hoisted up</h3>

<p>As JavaScript developers, we expect functions (that are blocks of code) to be hoisted up in their own lexical scope. Sadly, classes do not share this same property, which leads to workarounds like these:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">MyClass</span> <span class="o">=</span> <span class="kd">class</span> <span class="nx">MyClassInternal</span> <span class="p">{</span>
  <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p>This is not an issue when using module-oriented development. But if you are not using modules, then this becomes a hassle.</p>

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

<p>Here is an example class that we can rewrite:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Dog</span> <span class="p">{</span>
  <span class="kd">static</span> <span class="nx">AVERAGE_HEIGHT_FT</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
  <span class="kd">static</span> <span class="nx">AVERAGE_WEIGHT_KG</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
  <span class="kd">static</span> <span class="nx">_PRIVATE_MAGIC_HEIGHT</span> <span class="o">=</span> <span class="mf">3.14</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">height</span><span class="p">,</span> <span class="nx">weight</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">height</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">weight</span> <span class="o">=</span> <span class="nx">weight</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">height</span> <span class="o">===</span> <span class="k">this</span><span class="p">.</span><span class="nx">_PRIVATE_MAGIC_HEIGHT</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">_privateMakeTaller</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">_privateMakeTaller</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">Dog</span><span class="p">.</span><span class="nx">AVERAGE_HEIGHT_FT</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">printHeight</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">status</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">height</span> <span class="o">&gt;</span> <span class="nx">Dog</span><span class="p">.</span><span class="nx">AVERAGE_HEIGHT_FT</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">taller</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">not taller</span><span class="dl">'</span><span class="p">;</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Your dog is </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">height</span><span class="p">}</span><span class="s2"> ft tall. The dog is </span><span class="p">${</span><span class="nx">status</span><span class="p">}</span><span class="s2"> than the average height`</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="kd">static</span> <span class="nx">getAverageHeight</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Average height for dogs is </span><span class="p">${</span><span class="nx">Dog</span><span class="p">.</span><span class="nx">AVERAGE_HEIGHT_FT</span><span class="p">}</span><span class="s2"> ft`</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This code has a mix of static and instance-level methods and properties. Let us assume we want to expose only three methods: <code class="language-plaintext highlighter-rouge">constructor</code>, <code class="language-plaintext highlighter-rouge">printHeight</code> and <code class="language-plaintext highlighter-rouge">getAverageHeight</code>. Notice the weight properties are unused, which we might automatically detect in our re-written version. We also have a secret method and a secret property, that we want to encapsulate well.</p>

<h2 id="closures-to-the-rescue">Closures to the rescue!</h2>

<p>JavaScript closures are amazing. We can use them to emulate static properties, instance properties, private properties, as well as readonly properties. Here’s the rewritten version of the above class:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">Dog</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span> <span class="nx">createDogClass</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">AVERAGE_HEIGHT_FT</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">AVERAGE_WEIGHT_KG</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">_PRIVATE_MAGIC_HEIGHT</span> <span class="o">=</span> <span class="mf">3.14</span><span class="p">;</span>

  <span class="kd">function</span> <span class="nx">init</span><span class="p">(</span><span class="nx">heightInput</span><span class="p">,</span> <span class="nx">weightInput</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">height</span> <span class="o">=</span> <span class="nx">heightInput</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">weight</span> <span class="o">=</span> <span class="nx">weightInput</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">height</span> <span class="o">===</span> <span class="nx">_PRIVATE_MAGIC_HEIGHT</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">_privateMakeTaller</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="kd">function</span> <span class="nx">_privateMakeTaller</span><span class="p">()</span> <span class="p">{</span>
      <span class="nx">height</span> <span class="o">=</span> <span class="nx">AVERAGE_HEIGHT_FT</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">function</span> <span class="nx">printHeight</span><span class="p">()</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">status</span> <span class="o">=</span> <span class="nx">height</span> <span class="o">&gt;</span> <span class="nx">AVERAGE_HEIGHT_FT</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">taller</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">not taller</span><span class="dl">'</span><span class="p">;</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Your dog is </span><span class="p">${</span><span class="nx">height</span><span class="p">}</span><span class="s2"> ft tall. The dog is </span><span class="p">${</span><span class="nx">status</span><span class="p">}</span><span class="s2"> than the average height`</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">{</span>
      <span class="nx">printHeight</span><span class="p">,</span>
    <span class="p">};</span>
  <span class="p">}</span>

  <span class="kd">function</span> <span class="nx">getAverageHeight</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Average height for dogs is </span><span class="p">${</span><span class="nx">AVERAGE_HEIGHT_FT</span><span class="p">}</span><span class="s2"> kg`</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="p">{</span>
    <span class="nx">init</span><span class="p">,</span>
    <span class="nx">getAverageHeight</span><span class="p">,</span>
  <span class="p">};</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>Immediately, we see several advantages:</p>

<ol>
  <li>Unused properties are flagged by eslint (<a href="https://eslint.org/play/#eyJ0ZXh0IjoiY29uc3QgRG9nID0gKCgpID0+IHtcbiAgY29uc3QgQVZFUkFHRV9IRUlHSFRfRlQgPSA0O1xuICBjb25zdCBBVkVSQUdFX1dFSUdIVF9LRyA9IDEwMDtcbiAgY29uc3QgX1BSSVZBVEVfTUFHSUNfSEVJR0hUID0gMy4xNDtcblxuICBmdW5jdGlvbiBpbml0KGhlaWdodElucHV0LCB3ZWlnaHRJbnB1dCkge1xuICAgIGxldCBoZWlnaHQgPSBoZWlnaHRJbnB1dDtcbiAgICBjb25zdCB3ZWlnaHQgPSB3ZWlnaHRJbnB1dDtcbiAgICBpZiAoaGVpZ2h0ID09PSBfUFJJVkFURV9NQUdJQ19IRUlHSFQpIHtcbiAgICAgIF9wcml2YXRlTWFrZVRhbGxlcigpO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIF9wcml2YXRlTWFrZVRhbGxlcigpIHtcbiAgICAgIGhlaWdodCA9IEFWRVJBR0VfSEVJR0hUX0ZUICsgMTtcbiAgICB9XG5cbiAgICBmdW5jdGlvbiBwcmludEhlaWdodCgpIHtcbiAgICAgIGNvbnN0IHN0YXR1cyA9IGhlaWdodCA+IEFWRVJBR0VfSEVJR0hUX0ZUID8gJ3RhbGxlcicgOiAnbm90IHRhbGxlcic7XG4gICAgICBjb25zb2xlLmxvZyhgWW91ciBkb2cgaXMgJHtoZWlnaHR9IGZ0IHRhbGwuIFRoZSBkb2cgaXMgJHtzdGF0dXN9IHRoYW4gdGhlIGF2ZXJhZ2UgaGVpZ2h0YCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIHByaW50SGVpZ2h0LFxuICAgIH07XG4gIH1cblxuICBmdW5jdGlvbiBnZXRBdmVyYWdlSGVpZ2h0KCkge1xuICAgIGNvbnNvbGUubG9nKGBBdmVyYWdlIGhlaWdodCBmb3IgZG9ncyBpcyAke0FWRVJBR0VfSEVJR0hUX0ZUfSBrZ2ApO1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBpbml0LFxuICAgIGdldEF2ZXJhZ2VIZWlnaHQsXG4gIH07XG59KSgpOyIsIm9wdGlvbnMiOnsiZW52Ijp7ImVzNiI6dHJ1ZX0sInBhcnNlck9wdGlvbnMiOnsiZWNtYUZlYXR1cmVzIjp7fSwiZWNtYVZlcnNpb24iOiJsYXRlc3QiLCJzb3VyY2VUeXBlIjoic2NyaXB0In0sInJ1bGVzIjp7ImNvbnN0cnVjdG9yLXN1cGVyIjpbImVycm9yIl0sImZvci1kaXJlY3Rpb24iOlsiZXJyb3IiXSwiZ2V0dGVyLXJldHVybiI6WyJlcnJvciJdLCJuby1hc3luYy1wcm9taXNlLWV4ZWN1dG9yIjpbImVycm9yIl0sIm5vLWNhc2UtZGVjbGFyYXRpb25zIjpbImVycm9yIl0sIm5vLWNsYXNzLWFzc2lnbiI6WyJlcnJvciJdLCJuby1jb21wYXJlLW5lZy16ZXJvIjpbImVycm9yIl0sIm5vLWNvbmQtYXNzaWduIjpbImVycm9yIl0sIm5vLWNvbnN0LWFzc2lnbiI6WyJlcnJvciJdLCJuby1jb25zdGFudC1jb25kaXRpb24iOlsiZXJyb3IiXSwibm8tY29udHJvbC1yZWdleCI6WyJlcnJvciJdLCJuby1kZWJ1Z2dlciI6WyJlcnJvciJdLCJuby1kZWxldGUtdmFyIjpbImVycm9yIl0sIm5vLWR1cGUtYXJncyI6WyJlcnJvciJdLCJuby1kdXBlLWNsYXNzLW1lbWJlcnMiOlsiZXJyb3IiXSwibm8tZHVwZS1lbHNlLWlmIjpbImVycm9yIl0sIm5vLWR1cGUta2V5cyI6WyJlcnJvciJdLCJuby1kdXBsaWNhdGUtY2FzZSI6WyJlcnJvciJdLCJuby1lbXB0eSI6WyJlcnJvciJdLCJuby1lbXB0eS1jaGFyYWN0ZXItY2xhc3MiOlsiZXJyb3IiXSwibm8tZW1wdHktcGF0dGVybiI6WyJlcnJvciJdLCJuby1leC1hc3NpZ24iOlsiZXJyb3IiXSwibm8tZXh0cmEtYm9vbGVhbi1jYXN0IjpbImVycm9yIl0sIm5vLWV4dHJhLXNlbWkiOlsiZXJyb3IiXSwibm8tZmFsbHRocm91Z2giOlsiZXJyb3IiXSwibm8tZnVuYy1hc3NpZ24iOlsiZXJyb3IiXSwibm8tZ2xvYmFsLWFzc2lnbiI6WyJlcnJvciJdLCJuby1pbXBvcnQtYXNzaWduIjpbImVycm9yIl0sIm5vLWlubmVyLWRlY2xhcmF0aW9ucyI6WyJlcnJvciJdLCJuby1pbnZhbGlkLXJlZ2V4cCI6WyJlcnJvciJdLCJuby1pcnJlZ3VsYXItd2hpdGVzcGFjZSI6WyJlcnJvciJdLCJuby1sb3NzLW9mLXByZWNpc2lvbiI6WyJlcnJvciJdLCJuby1taXNsZWFkaW5nLWNoYXJhY3Rlci1jbGFzcyI6WyJlcnJvciJdLCJuby1taXhlZC1zcGFjZXMtYW5kLXRhYnMiOlsiZXJyb3IiXSwibm8tbmV3LXN5bWJvbCI6WyJlcnJvciJdLCJuby1ub25vY3RhbC1kZWNpbWFsLWVzY2FwZSI6WyJlcnJvciJdLCJuby1vYmotY2FsbHMiOlsiZXJyb3IiXSwibm8tb2N0YWwiOlsiZXJyb3IiXSwibm8tcHJvdG90eXBlLWJ1aWx0aW5zIjpbImVycm9yIl0sIm5vLXJlZGVjbGFyZSI6WyJlcnJvciJdLCJuby1yZWdleC1zcGFjZXMiOlsiZXJyb3IiXSwibm8tc2VsZi1hc3NpZ24iOlsiZXJyb3IiXSwibm8tc2V0dGVyLXJldHVybiI6WyJlcnJvciJdLCJuby1zaGFkb3ctcmVzdHJpY3RlZC1uYW1lcyI6WyJlcnJvciJdLCJuby1zcGFyc2UtYXJyYXlzIjpbImVycm9yIl0sIm5vLXRoaXMtYmVmb3JlLXN1cGVyIjpbImVycm9yIl0sIm5vLXVuZGVmIjpbImVycm9yIl0sIm5vLXVuZXhwZWN0ZWQtbXVsdGlsaW5lIjpbImVycm9yIl0sIm5vLXVucmVhY2hhYmxlIjpbImVycm9yIl0sIm5vLXVuc2FmZS1maW5hbGx5IjpbImVycm9yIl0sIm5vLXVuc2FmZS1uZWdhdGlvbiI6WyJlcnJvciJdLCJuby11bnNhZmUtb3B0aW9uYWwtY2hhaW5pbmciOlsiZXJyb3IiXSwibm8tdW51c2VkLWxhYmVscyI6WyJlcnJvciJdLCJuby11bnVzZWQtdmFycyI6WyJlcnJvciJdLCJuby11c2VsZXNzLWJhY2tyZWZlcmVuY2UiOlsiZXJyb3IiXSwibm8tdXNlbGVzcy1jYXRjaCI6WyJlcnJvciJdLCJuby11c2VsZXNzLWVzY2FwZSI6WyJlcnJvciJdLCJuby13aXRoIjpbImVycm9yIl0sInJlcXVpcmUteWllbGQiOlsiZXJyb3IiXSwidXNlLWlzbmFuIjpbImVycm9yIl0sInZhbGlkLXR5cGVvZiI6WyJlcnJvciJdLCJuby11bnVzZWQtcHJpdmF0ZS1jbGFzcy1tZW1iZXJzIjpbImVycm9yIl19fX0=">see example</a>)</li>
  <li>Webpack optimizes our code with variable renaming and dead code removal.</li>
  <li>We have lesser code bloat thanks to removing <code class="language-plaintext highlighter-rouge">this.</code> and <code class="language-plaintext highlighter-rouge">Dog.</code> prefixes.</li>
  <li>
    <s>Our private and readonly properties are truly private and readonly now.</s>
    <p>As we discussed earlier, regular classes also support this.</p>
  </li>
</ol>

<h2 id="how-does-this-work">How does this work?</h2>

<h3 id="static-scope">Static scope</h3>

<p>The scope inside <code class="language-plaintext highlighter-rouge">createDogClass</code> is the <code class="language-plaintext highlighter-rouge">static</code> scope. Variables and functions declared in this scope are shared by all instances. From this scope, we return an object of properties that are exposed externally. In this case, we return:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">init</code> which acts as our constructor, and</li>
  <li><code class="language-plaintext highlighter-rouge">getAverageHeight</code> which lets the users know the average height of dogs.</li>
</ol>

<p>Notice how:</p>

<ol>
  <li>the magic height property is private, and</li>
  <li>the average height property is public (exposed via <code class="language-plaintext highlighter-rouge">getAverageHeight</code>) but readonly</li>
</ol>

<h3 id="instance-level-scope">Instance-level scope</h3>

<p>We replaced the <code class="language-plaintext highlighter-rouge">constructor</code> with an <code class="language-plaintext highlighter-rouge">init</code> function that does the same job. Now, to create an instance, we call <code class="language-plaintext highlighter-rouge">Dog.init(...)</code> (instead of <code class="language-plaintext highlighter-rouge">new Dog(...)</code>). Note that each invocation of <code class="language-plaintext highlighter-rouge">init</code> returns a new object instance, which also comes up with a separate lexical scope (very handy for us!)</p>

<p>From inside <code class="language-plaintext highlighter-rouge">init</code>, we return all publicly exposed properties. In this case, we only expose <code class="language-plaintext highlighter-rouge">printHeight</code>.  Notice how:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">_privateMakeTaller</code> is a private method, and unique for every instance of <code class="language-plaintext highlighter-rouge">Dog</code></li>
  <li>the static properties from <code class="language-plaintext highlighter-rouge">Dog</code> scope are easily accessible.</li>
  <li><code class="language-plaintext highlighter-rouge">height</code> and <code class="language-plaintext highlighter-rouge">weight</code> values are not shared across instances (unique per instance)</li>
</ol>

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

<p>I hope this post helped you understand how to rewrite JavaScript <code class="language-plaintext highlighter-rouge">class</code> syntax into closures.</p>

<h2 id="post-conclusion">Post conclusion</h2>

<p>Motivated by the HN comments, here’s some additional considerations when using closure syntax to emulate classes:</p>

<ol>
  <li>Developer experience takes a hit because now the <code class="language-plaintext highlighter-rouge">instanceof</code> check no longer works. Further, the syntax <code class="language-plaintext highlighter-rouge">Dog.init</code> might be strange to use instead of the familiar <code class="language-plaintext highlighter-rouge">new Dog</code></li>
  <li>Every new object ships with a copy of all the methods, which is bad for memory optimization of the program.</li>
</ol>

<p>Also, I don’t recommend that we start switching all classes to closures straightaway. With this blog post, I want to highlight the difference between the class pattern vs the closure pattern, and how each of them have their differences and advantages. The choice of which one to use is ultimately yours to make.</p>

<h2 id="further-discussion">Further discussion</h2>

<p>Mika Genic reached out to me via e-mail to provide a clever design that clearly defines a public interface while also avoiding an IIFE. Here’s an example:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">Dog</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// define public interface</span>
  <span class="kd">const</span> <span class="nb">self</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="p">{</span>
    <span class="nx">publicFn1</span><span class="p">,</span>
  <span class="p">})</span>

  <span class="c1">// init private state</span>
  <span class="kd">let</span> <span class="nx">created</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
 
  <span class="c1">// call constructor</span>
  <span class="kd">constructor</span><span class="p">()</span>

  <span class="k">return</span> <span class="nb">self</span>
 
  <span class="c1">// logic</span>
  <span class="kd">function</span> <span class="kd">constructor</span><span class="p">()</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`dog created`</span><span class="p">)</span> <span class="p">}</span>
  <span class="kd">function</span> <span class="nx">publicFn1</span><span class="p">()</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">privateFn1</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span> <span class="p">}</span>
  <span class="kd">function</span> <span class="nx">privateFn1</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">created</span><span class="p">}</span><span class="s2">`</span> <span class="p">}</span>
<span class="p">}</span>

<span class="kd">let</span> <span class="nx">dog</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Dog</span><span class="p">(</span><span class="s2">`Billy`</span><span class="p">)</span>
<span class="nx">dog</span><span class="p">.</span><span class="nx">publicFn1</span><span class="p">()</span>
</code></pre></div></div>

<p>Note that:</p>

<ol>
  <li>there is no IIFE, which makes understanding the code far simpler.</li>
  <li>the public interface is clearly separated from the rest of the logic</li>
  <li>You can use the traditional <code class="language-plaintext highlighter-rouge">new</code> keyword to construct objects from this class.</li>
  <li>You can avoid <code class="language-plaintext highlighter-rouge">this</code> entirely.</li>
</ol>

<p>Some readers will notice that this is very similar to the pre-ES6 way of declaring classes. Here’s an <a href="https://stackoverflow.com/a/387733">example post on StackOverflow</a>.</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[Update]]></summary></entry><entry><title type="html">Why is it so hard to read a million numbers in PowerShell?</title><link href="gaurangtandon.com/blog/powershell-get-content" rel="alternate" type="text/html" title="Why is it so hard to read a million numbers in PowerShell?" /><published>2022-09-12T06:50:52+00:00</published><updated>2022-09-12T06:50:52+00:00</updated><id>gaurangtandon.com/blog/powershell-getcontent</id><content type="html" xml:base="gaurangtandon.com/blog/powershell-get-content"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>…or why you should always double-check that StackOverflow answer you just read.</p>

<p>This is a short story of how a slow PowerShell command led me to its .NET internals.</p>

<p>I have to run an executable file with dedicated input and output files. For context, this executable is compiled from C++ (can also use Python), and it will read a million numbers from an input file and then output their sum to another file. I prefer to use I/O redirection for this task as it is semantically most correct in this context.</p>

<p>In Linux, I would simply do <code class="language-plaintext highlighter-rouge">.\A.out &lt; .\A.in &gt; .\A.out</code>. However, the input redirect (<code class="language-plaintext highlighter-rouge">&lt;</code>) operator is “reserved for future use”, even in PS7! They probably just forgot to implement it ._.</p>

<p>Either way, I search for alternatives and find the <a href="https://stackoverflow.com/a/11788475/2181238">first StackOverflow answer</a>. Happily, I run its suggested command:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Content</span><span class="w"> </span><span class="o">.</span><span class="nx">\A.in</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">.</span><span class="n">\A.exe</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="o">.</span><span class="nx">\A.out</span><span class="w">
</span></code></pre></div></div>

<p>Now, pause and think. What can go wrong here? The PowerShell gurus probably know the answer, but the Linux/macOS users are in for an interesting investigation!</p>

<h2 id="what-went-wrong">What went wrong</h2>

<p>This command took a <strong>whopping 15 seconds</strong>! Clearly, no modern CPU should take 15 seconds to sum up a million numbers. So how did this happen?</p>

<h2 id="hypotheses">Hypotheses</h2>

<p>Arithmetic is very fast for fast CPUs. So, there <strong>has</strong> to be an issue with the I/O redirection being too slow. To test that, I wrote a different C program that uses <code class="language-plaintext highlighter-rouge">freopen</code> to redirect the input file to stdin. This program only took 0.36seconds, neat!</p>

<p>What if the issue is some weird MSVC implementation issue? So, I re-wrote the same program in Python, with similar timing results (slower with <code class="language-plaintext highlighter-rouge">Get-Content</code>, faster otherwise).</p>

<h2 id="analyzing-the-stackoverflow-answer">Analyzing the StackOverflow answer</h2>

<p>It is clear that Get-Content is taking too long. But how can it be slow? The answer we <del>copied</del> read had over hundred upvotes, was over a decade old, and was viewed at least 70k times. It also had a comment explaining <code class="language-plaintext highlighter-rouge">Get-Content</code> sends the pipes the lines one by one.</p>

<p>Let’s check if at least that is true. A program that reads only the first ten lines of the file should output instantly. Here’s a sample:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">accumulated_input</span> <span class="o">=</span> <span class="s">""</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
  <span class="n">accumulated_input</span> <span class="o">+=</span> <span class="nb">input</span><span class="p">()</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span>
<span class="k">print</span><span class="p">(</span><span class="n">accumulated_input</span><span class="p">)</span>
</code></pre></div></div>

<p>We run this with <code class="language-plaintext highlighter-rouge">Get-Content .\A.in | python3 .\code.py</code>. The program instantly prints the first ten lines!</p>

<p>Now we know that the StackOverflow answer is correct. What then is really wrong with <code class="language-plaintext highlighter-rouge">Get-Content</code>?</p>

<h2 id="notice-the-slowness">Notice the slowness</h2>

<p>Note that the above program did <em>not</em> exit after printing the first ten numbers, which tells us that <code class="language-plaintext highlighter-rouge">Get-Content</code> was <em>still</em> running. In fact, our root issue is that <code class="language-plaintext highlighter-rouge">Get-Content</code> <em>itself</em> is just <strong>incredibly</strong> slow.</p>

<p>Searching for “why is powershell get-content so slow” reveals <a href="https://joelitechlife.ca/2022/06/08/powershell-get-content-slow/">this blog</a> that demystifies the snail speed. <code class="language-plaintext highlighter-rouge">Get-Content</code> adds a bunch of metadata (called <code class="language-plaintext highlighter-rouge">NoteProperty</code>) to the data it reads. Because it reads one line at a time, it adds metadata to every line it reads. For a file with a million lines, this metadata addition becomes painfully slow.</p>

<p>Luckily, we can force the metadata to be added in batches of lines. For example, let’s run the following command that adds the <code class="language-plaintext highlighter-rouge">NoteProperty</code>s in batches of a thousand lines:</p>

<p><code class="language-plaintext highlighter-rouge">Get-Content .\A.in -ReadCount 1000 | .\A.exe &gt; .\A.out</code></p>

<p>This is faster than our original code, but still takes over <strong>three seconds</strong> to complete, which is completely unacceptable. Interestingly, increasing the <code class="language-plaintext highlighter-rouge">ReadCount</code> slows the program even further.</p>

<p>Which means we need to continue digging deeper…</p>

<h2 id="diving-into-net-internals">Diving into .NET internals</h2>

<p><a href="https://docs.microsoft.com/en-us/powershell/scripting/overview?view=powershell-7.2"><strong>What is PowerShell?</strong></a> Primarily, it is a scripting language “built on the .NET Common Language Runtime (CLR). All inputs and outputs are .NET objects”.</p>

<p><code class="language-plaintext highlighter-rouge">Get-Content</code> is a high-level function exposed to us, that is unusable for larger files and has no other high-level alternatives. So, we dive into the .NET internal classes.</p>

<p>There are various <code class="language-plaintext highlighter-rouge">Read</code> methods in <code class="language-plaintext highlighter-rouge">System.IO.File</code>, such as: <code class="language-plaintext highlighter-rouge">ReadAllBytes</code>, <code class="language-plaintext highlighter-rouge">ReadAllLines</code>, <code class="language-plaintext highlighter-rouge">ReadLines</code>, however, the method most relevant to us is: <a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.file.readalltext?view=net-6.0"><code class="language-plaintext highlighter-rouge">System.IO.File::ReadAllText</code></a>. This method simply “opens a text file, reads all the text in the file into a string, and then closes the file”. For extremely large files, this may not fit into the memory. However, for only a million numbers, this is good enough. So, we now run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Measure-Command</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.File</span><span class="p">]::</span><span class="n">ReadAllText</span><span class="p">(</span><span class="s1">'.\A.in'</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">.</span><span class="n">\A.exe</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="nx">A.out</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This <strong>completes in 0.3seconds</strong>, just as fast as our Linux counterpart! 🎉</p>

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

<p>Always double check StackOverflow answers for critical cases. It cost me one problem out of four in Hacker Cup 2022. To be clear, the answer was not wrong, just that my use case for it was different. Fortunately, I qualified the round either way ^_^</p>

<!-- TODO:
- [ ] fix program filenames
- [ ] post to HN -->]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[Introduction]]></summary></entry></feed>