<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://alexyorke.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://alexyorke.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-03-29T03:03:17+00:00</updated><id>https://alexyorke.github.io/feed.xml</id><title type="html">Alex Yorke</title><subtitle>Engineering writing on CI/CD, testing, reliability, and functional programming.</subtitle><author><name>Alex Yorke</name></author><entry><title type="html">How I escaped decision hell by using a 1972 cognitive theory</title><link href="https://alexyorke.github.io/2025/09/26/how-i-escaped-decision-hell/" rel="alternate" type="text/html" title="How I escaped decision hell by using a 1972 cognitive theory" /><published>2025-09-26T00:00:00+00:00</published><updated>2025-09-26T00:00:00+00:00</updated><id>https://alexyorke.github.io/2025/09/26/how-i-escaped-decision-hell</id><content type="html" xml:base="https://alexyorke.github.io/2025/09/26/how-i-escaped-decision-hell/"><![CDATA[<p>Making decisions is an inherent part of engineering. That might mean picking a database, a message queue, or a CSV library that will run in production against customer data. When several options are viable but trade-offs differ, you need a pragmatic way to narrow the field. The goal is to decide quickly, logically, and effectively.</p>

<p>One useful strategy is <strong>Elimination by Aspects (EBA)</strong>. This approach helps you narrow down options systematically until the choice becomes clear. The goal is to show how you can quickly get the “shape” of a problem, the rough contours of the solution space, without exhaustive analysis of every option.</p>

<h2 id="what-is-elimination-by-aspects">What is Elimination by Aspects?</h2>

<p>Elimination by Aspects is a decision-making heuristic introduced by psychologist Amos Tversky in 1972. The core idea is to gradually whittle down a list of alternatives by applying one criterion (aspect) at a time. Then you move to the next criterion, and so on, until you’re left with a manageable shortlist or even a single winner. In other words, you filter options sequentially by your must-have requirements.</p>

<p><strong>Key characteristics of EBA:</strong></p>

<ul>
  <li>It’s an iterative, <strong>non-compensatory</strong> process. This means an option that fails one essential criterion is out, no matter how great it is on other factors.</li>
  <li>You’re effectively making a series of <strong>yes/no decisions</strong> on each aspect, rather than comparing everything against everything at once.</li>
</ul>

<h3 id="example-buying-a-lawnmower-with-eba">Example: Buying a Lawnmower with EBA</h3>

<p>To illustrate the process, let’s use a non-software example first: buying a lawnmower.</p>

<ol>
  <li><strong>Type of Mower:</strong> Is it a ride-on, robotic, or push mower?</li>
  <li><strong>Yard Size:</strong> How large is the area you need to mow?</li>
  <li><strong>Terrain:</strong> Do you need to mow on steep slopes (15° or more)?</li>
  <li><strong>Power &amp; Maintenance:</strong> Do you prefer gas engine power or electric (battery/corded)? And how much maintenance are you okay with?</li>
  <li><strong>Budget Range:</strong> What’s your budget ceiling?</li>
</ol>

<p>By sequentially applying these aspects, you might cut the options down from thousands to a handful. Essentially, you ask the most consequential questions first (those that divide the field the most). EBA works best when you choose aspects that maximize information gain early in the process.</p>

<p>I found that ChatGPT Thinking/Pro, or Gemini 2.5 Pro (both with web search enabled) generated pretty good EBA-style questions, the prompt I used was “What are the top 5 questions to narrow down my lawnmower search?”.</p>

<h3 id="example-selecting-a-csv-parser-library-with-eba">Example: Selecting a CSV Parser Library with EBA</h3>

<p>Now let’s apply EBA to a software engineering decision. Suppose you need to choose a CSV parsing library for a project. Here’s how an elimination-by-aspects strategy might look:</p>

<ol>
  <li><strong>Start with the Universe of Options:</strong> Do a broad search filtered by your programming language to list all CSV parser libraries that could be relevant. This is your initial pool.</li>
  <li><strong>Suitability for Production:</strong> Immediately discard any libraries that look obviously unsuitable for production use.</li>
  <li><strong>Basic Viability Check:</strong> Apply a few must-have sanity criteria to the remaining list:
    <ul>
      <li><strong>Compilation/Installation:</strong> The library should at least compile/build or install cleanly.</li>
      <li><strong>Popularity/Community Usage:</strong> While not a perfect metric, check if the library has at least a minimal level of adoption, for instance, a few hundred stars on GitHub or a decent number of weekly downloads on NPM/PyPI.</li>
      <li><strong>Documentation:</strong> If there’s no README or documentation, that’s a huge red flag.</li>
      <li><strong>When was the package published, was it yesterday?</strong> If it’s been out for a while, then there is more time for people to report the package being malicious, etc.</li>
    </ul>
  </li>
  <li><strong>Feature and Performance Requirements:</strong> With a shorter list in hand, introduce more specific criteria based on your project’s needs:
    <ul>
      <li><strong>Performance:</strong> Do you need to parse very large CSV files or do streaming?</li>
      <li><strong>Features:</strong> Identify required features (e.g., does it handle quoted fields correctly? Can it parse into custom data types or handle different delimiters? Does it also support writing CSV, if you need that?).</li>
      <li><strong>Robustness:</strong> Consider how the library handles malformed data or edge cases (like newline characters within fields, missing values, etc.).</li>
      <li><strong>Dependencies:</strong> Does the library drag in huge external dependencies or native modules?</li>
      <li><strong>Maintenance:</strong> Is the library actively maintained?</li>
    </ul>
  </li>
  <li><strong>Final Selection:</strong> By this point, you’ve likely narrowed it down to a handful (or even a single) candidate that meets all your aspects.</li>
</ol>

<blockquote>
  <p>I applied elimination by aspects to 200 npmjs libraries tagged with “csv” by attaching all 200 readmes to a Gemini 2.5 Pro chat, here is how I narrowed them down:</p>
</blockquote>

<p><img src="/assets/images/eba_diagram.png" alt="EBA filtering diagram" /></p>

<p>Further refinement for “server-side, not browser-based user downloads” reduced the choices to 30. This iterative process allowed for increasingly specific filtering based on desired functionality. You can select multiple branches in parallel as well if you’re unsure which direction to take or skip questions.</p>

<p>If you’re not willing to download 200 readmes (I don’t blame you), the prompt I used for Gemini was “use elimination by aspects questions to tell me which library I should use based on my answers to your questions to select a csv parser library on npmjs.org.” and then it interviewed me and chose a library for me based on my answers.</p>

<h2 id="benefits-of-using-eba-in-tech-decisions">Benefits of Using EBA in Tech Decisions</h2>

<p>Employing elimination-by-aspects in software engineering decisions offers several benefits:</p>

<ul>
  <li><strong>Reduces Overwhelm:</strong> By focusing on one criterion at a time, you avoid the mental burnout of weighing every factor of every option simultaneously.</li>
  <li><strong>Ensures Must-Haves Are Met:</strong> EBA forces you to identify and prioritize your non-negotiable requirements up front.</li>
  <li><strong>Transparent and Defensible Process:</strong> The step-by-step nature of EBA makes your decision process transparent.</li>
  <li><strong>Reduces Bias, Promotes Objectivity:</strong> Deciding on criteria before getting enamored with a particular option can mitigate knee-jerk bias toward a familiar or “shiny new” technology.</li>
</ul>

<h2 id="caveats-and-limitations-to-watch-for">Caveats and Limitations to Watch For</h2>

<p>No decision technique is perfect. Keep these caveats in mind when using elimination by aspects:</p>

<ul>
  <li><strong>Non-Compensatory = No Trade-Offs:</strong> Because EBA is non-compensatory, an otherwise great option will get tossed out if it fails on one chosen criterion. <em>Tip: Choose your elimination aspects carefully and make sure each one really is a deal-breaker.</em></li>
  <li><strong>Order Matters:</strong> The sequence in which you apply criteria can affect the outcome. It’s usually wise to start with the highest priority aspect, essentially, assert “if it doesn’t have X, nothing else matters” only for truly fundamental X’s.</li>
  <li><strong>Requires Clear, Measurable Criteria:</strong> EBA works best when your aspects are well-defined. For instance, define scalability as “must handle &gt;10k requests/sec” or security as “must have no critical vuln reports in the last year”, whatever fits your context.</li>
  <li><strong>May Not Yield a Unique Winner:</strong> Sometimes you’ll go through your list of aspects and still end up with a tie or a few viable candidates. If multiple options survive all your filters, you can then switch to comparing them on secondary attributes or even doing a proof-of-concept with each.</li>
</ul>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>Elimination by aspects is a handy tool in the decision-making toolbox for software engineers. Whenever you’re faced with a daunting list of technologies or design choices, think in terms of aspects: figure out your must-haves, and start chopping off options that don’t check those boxes.</p>

<p>In the fast-moving tech world, where new libraries and frameworks pop up weekly, this approach can help you and your team avoid analysis paralysis and make decisions with confidence.</p>

<p>Ultimately, elimination by aspects won’t guarantee a perfect choice (no method can), but it will give you a rational, repeatable process to arrive at a good choice that meets your needs. It provides practicality and efficiency in decision-making, even if it may not lead to the absolute optimal outcome in hindsight.</p>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[Explains Elimination by Aspects (Tversky, 1972) as a practical way to make engineering decisions quickly by filtering options with must-have criteria.]]></summary></entry><entry><title type="html">Monads in C# (Part 2): Result</title><link href="https://alexyorke.github.io/2025/09/13/monads-in-c-sharp-part-2-result/" rel="alternate" type="text/html" title="Monads in C# (Part 2): Result" /><published>2025-09-13T00:00:00+00:00</published><updated>2025-09-13T00:00:00+00:00</updated><id>https://alexyorke.github.io/2025/09/13/monads-in-c-sharp-part-2-result</id><content type="html" xml:base="https://alexyorke.github.io/2025/09/13/monads-in-c-sharp-part-2-result/"><![CDATA[<p><strong>Previously in the series</strong>: <a href="https://alexyorke.github.io/2025/06/29/list-is-a-monad/">List is a monad (part 1)</a></p>

<blockquote>
  <p><em>Note</em>: rewritten 2025-12-28.</p>
</blockquote>

<p>In <strong>Part 1</strong> (<code class="language-plaintext highlighter-rouge">List</code>), we contrasted <code class="language-plaintext highlighter-rouge">Map</code> (<code class="language-plaintext highlighter-rouge">Select</code>) vs <code class="language-plaintext highlighter-rouge">Bind</code> (<code class="language-plaintext highlighter-rouge">SelectMany</code>) on <code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code>, then built <code class="language-plaintext highlighter-rouge">Maybe&lt;T&gt;</code>.</p>

<p>If you read Part 1, you already know the shape: <code class="language-plaintext highlighter-rouge">Bind</code>/<code class="language-plaintext highlighter-rouge">SelectMany</code> chains steps, and <code class="language-plaintext highlighter-rouge">Maybe</code> decides whether the next step runs.</p>

<p>The <code class="language-plaintext highlighter-rouge">Result</code> monad<sup id="fnref:result-monad-precise" role="doc-noteref"><a href="#fn:result-monad-precise" class="footnote" rel="footnote">1</a></sup> lets you compose computations that can fail. You return <code class="language-plaintext highlighter-rouge">Ok(value)</code> or <code class="language-plaintext highlighter-rouge">Fail(error)</code>, then compose with <code class="language-plaintext highlighter-rouge">Bind</code> to propagate the first failure (later steps don’t run; the failure just flows through).<sup id="fnref:shortcircuit" role="doc-noteref"><a href="#fn:shortcircuit" class="footnote" rel="footnote">2</a></sup> It’s useful for <strong>making expected failure explicit and composable</strong>.</p>

<p><code class="language-plaintext highlighter-rouge">Result&lt;TSuccess, TError&gt;</code> has the same <em>two-case</em> shape as <code class="language-plaintext highlighter-rouge">Maybe&lt;T&gt;</code>, except the non-success case carries a reason (<code class="language-plaintext highlighter-rouge">TError</code>). <code class="language-plaintext highlighter-rouge">Maybe</code> models optionality; <code class="language-plaintext highlighter-rouge">Result</code> models failure <em>with</em> an explicit reason.</p>

<p>Prefer to compose with <code class="language-plaintext highlighter-rouge">Bind</code> until you need to branch, translate, or produce an output (often at a boundary/edge), then <code class="language-plaintext highlighter-rouge">Match</code>. <sup id="fnref:checked-exceptions" role="doc-noteref"><a href="#fn:checked-exceptions" class="footnote" rel="footnote">3</a></sup></p>

<p>If you need to accumulate many errors (e.g., form validation) or you have several first-class outcomes, <code class="language-plaintext highlighter-rouge">Result</code> may not be the best fit.<sup id="fnref:accumulation" role="doc-noteref"><a href="#fn:accumulation" class="footnote" rel="footnote">4</a></sup></p>

<p>If you’re coming from FP, this is closest to an <code class="language-plaintext highlighter-rouge">Either</code>/<code class="language-plaintext highlighter-rouge">Result</code>-style type.<sup id="fnref:either" role="doc-noteref"><a href="#fn:either" class="footnote" rel="footnote">5</a></sup></p>

<h3 id="tldr">TL;DR</h3>
<p>What it looks like (implementations for <code class="language-plaintext highlighter-rouge">User</code> left out for brevity):</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">record</span> <span class="nf">Error</span><span class="p">(</span><span class="kt">string</span> <span class="n">Code</span><span class="p">,</span> <span class="kt">string</span> <span class="n">Message</span><span class="p">);</span>
<span class="c1">// Assume `repo` is in scope.</span>

<span class="c1">// Creating results:</span>
<span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="n">okUser</span> <span class="p">=</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Ok</span><span class="p">(</span><span class="k">new</span> <span class="nf">User</span><span class="p">(</span><span class="cm">/* ... */</span><span class="p">));</span>
<span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="n">failed</span> <span class="p">=</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Fail</span><span class="p">(</span><span class="k">new</span> <span class="nf">Error</span><span class="p">(</span><span class="s">"NotFound"</span><span class="p">,</span> <span class="s">"User not found"</span><span class="p">));</span>

<span class="c1">// Assume:</span>
<span class="c1">// Result&lt;int, Error&gt; ParseId(string inputIdFromRequest)</span>
<span class="c1">// Result&lt;User, Error&gt; DeactivateDecision(User user)</span>

<span class="c1">// Returning a Result from a function:</span>
<span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="nf">FindUserOrFail</span><span class="p">(</span><span class="n">IUserRepo</span> <span class="n">repo</span><span class="p">,</span> <span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">User</span><span class="p">?</span> <span class="n">user</span> <span class="p">=</span> <span class="n">repo</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">user</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Fail</span><span class="p">(</span><span class="k">new</span> <span class="nf">Error</span><span class="p">(</span><span class="s">"NotFound"</span><span class="p">,</span> <span class="s">$"User </span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="s"> not found"</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Ok</span><span class="p">(</span><span class="n">user</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="n">result</span> <span class="p">=</span>                   <span class="c1">// Result&lt;User, Error&gt;</span>
    <span class="nf">ParseId</span><span class="p">(</span><span class="n">inputIdFromRequest</span><span class="p">)</span>                <span class="c1">// Result&lt;int, Error&gt;, first in chain, always runs</span>
        <span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="n">id</span> <span class="p">=&gt;</span> <span class="nf">FindUserOrFail</span><span class="p">(</span><span class="n">repo</span><span class="p">,</span> <span class="n">id</span><span class="p">))</span>  <span class="c1">// Result&lt;User, Error&gt;, only runs if ParseId succeeded</span>
        <span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="n">DeactivateDecision</span><span class="p">);</span>             <span class="c1">// Result&lt;User, Error&gt;, only runs if FindUser succeeded</span>

<span class="c1">// Handle at the boundary (or when translating between layers):</span>
<span class="kt">string</span> <span class="n">message</span> <span class="p">=</span> <span class="n">result</span><span class="p">.</span><span class="nf">Match</span><span class="p">(</span>
    <span class="n">ok</span><span class="p">:</span>  <span class="n">_</span> <span class="p">=&gt;</span> <span class="s">"User deactivated"</span><span class="p">,</span>
    <span class="n">err</span><span class="p">:</span> <span class="n">e</span> <span class="p">=&gt;</span> <span class="s">$"Deactivate failed: </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Code</span><span class="p">}</span><span class="s"> - </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
</code></pre></div></div>

<p>On success, <code class="language-plaintext highlighter-rouge">Bind</code> passes the inner value to the next step; on failure, it forwards the <code class="language-plaintext highlighter-rouge">Error</code>. Short-circuiting allows the failure to propagate: after the first <code class="language-plaintext highlighter-rouge">Fail</code>, later steps are bypassed and the error flows through to the end. <code class="language-plaintext highlighter-rouge">Bind</code> <em>encodes</em> the control flow so your business logic doesn’t have to repeat it.</p>

<blockquote>
  <p><strong>Note:</strong> You may also see this described as “railway switching”, “bypassing”, “error propagation”, or “fail-fast”.</p>
</blockquote>

<p>Yes, this example uses a repository (a very .NET thing) and mutates a <code class="language-plaintext highlighter-rouge">User</code>. That’s deliberate: I don’t want this post to imply you should replace <code class="language-plaintext highlighter-rouge">exceptions</code> everywhere with <code class="language-plaintext highlighter-rouge">Result</code>.</p>

<p>The core idea is simple: <code class="language-plaintext highlighter-rouge">Bind</code> chains successes and short-circuits on the first failure.</p>

<h4 id="the-problem-explicit-vs-implicit">The problem: explicit vs. implicit</h4>

<p>In C#, fallible work is often handled with <code class="language-plaintext highlighter-rouge">exceptions</code>, or with explicit branching (<code class="language-plaintext highlighter-rouge">TryX</code>/guard clauses/status checks) depending on whether failure is expected.</p>

<p><strong>Option A: Implicit Control Flow (Exceptions)</strong></p>

<p>Method signatures often don’t advertise failure when using <code class="language-plaintext highlighter-rouge">exceptions</code>, unlike <code class="language-plaintext highlighter-rouge">TryX</code>/<code class="language-plaintext highlighter-rouge">bool</code>-return patterns.<sup id="fnref:checked-exceptions:1" role="doc-noteref"><a href="#fn:checked-exceptions" class="footnote" rel="footnote">3</a></sup>
<code class="language-plaintext highlighter-rouge">DeactivateUser</code> (below) returns <code class="language-plaintext highlighter-rouge">void</code>, so failures aren’t visible in the signature. In this style, it might throw for parsing/loading/saving and even for business-rule failures.</p>

<p>The following shows a style where expected failures are represented as <code class="language-plaintext highlighter-rouge">exceptions</code> (sometimes called “exceptions as control flow”; some may avoid this style). It’s intentionally heavy-handed.</p>

<p>Typical C# code is often closer to: parse with <code class="language-plaintext highlighter-rouge">TryParse</code>, call a repo/service that may throw, then catch once at the boundary.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The implicit "User" entity used in the examples below</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsActive</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">User</code> is <strong>mutable</strong> here to keep focus on <code class="language-plaintext highlighter-rouge">Result</code>. Prefer immutability where practical in domain code; some ecosystems (e.g., ORMs) still push you toward mutation.<sup id="fnref:immutability" role="doc-noteref"><a href="#fn:immutability" class="footnote" rel="footnote">6</a></sup></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">readonly</span> <span class="n">IUserRepo</span> <span class="n">_repo</span><span class="p">;</span>

<span class="k">public</span> <span class="k">void</span> <span class="nf">DeactivateUser</span><span class="p">(</span><span class="kt">string</span> <span class="n">inputId</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">inputId</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">inputId</span><span class="p">));</span>

    <span class="kt">int</span> <span class="n">id</span><span class="p">;</span>
    <span class="k">try</span>
    <span class="p">{</span>
        <span class="n">id</span> <span class="p">=</span> <span class="kt">int</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="n">inputId</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span> <span class="nf">when</span> <span class="p">(</span><span class="n">ex</span> <span class="k">is</span> <span class="n">FormatException</span> <span class="n">or</span> <span class="n">OverflowException</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">(</span><span class="s">"DeactivateUser failed at: parse id"</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">User</span><span class="p">?</span> <span class="n">user</span><span class="p">;</span>
    <span class="k">try</span>
    <span class="p">{</span>
        <span class="n">user</span> <span class="p">=</span> <span class="n">_repo</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">(</span><span class="s">"DeactivateUser failed at: load user"</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">user</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">(</span><span class="s">"User not found"</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(!</span><span class="n">user</span><span class="p">.</span><span class="n">IsActive</span><span class="p">)</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">(</span><span class="s">"User already inactive"</span><span class="p">);</span>

    <span class="n">user</span><span class="p">.</span><span class="n">IsActive</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
    <span class="k">try</span>
    <span class="p">{</span>
        <span class="n">_repo</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="n">user</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">(</span><span class="s">"DeactivateUser failed at: save user"</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Main point: in the code above, you’re responsible for <code class="language-plaintext highlighter-rouge">null</code> checks, initializing the user variable outside <code class="language-plaintext highlighter-rouge">try/catch</code>, and stopping (early return/throwing an <code class="language-plaintext highlighter-rouge">exception</code>). It’s noisy and easy to get wrong.</strong></p>

<p>In larger apps, <code class="language-plaintext highlighter-rouge">exceptions</code> often surface far from where you want domain context, so you either catch at boundaries or add local <code class="language-plaintext highlighter-rouge">try/catch</code> when you truly need extra context.</p>

<p><strong>Option B: Explicit Validation (Guard Clauses)</strong>
To avoid throwing for expected failures, you often use <code class="language-plaintext highlighter-rouge">TryX</code>-style APIs, guard clauses, and early returns. It’s linear, but still noisy.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">readonly</span> <span class="n">IUserRepo</span> <span class="n">_repo</span><span class="p">;</span>

<span class="k">public</span> <span class="k">enum</span> <span class="n">DeactivateUserResult</span>
<span class="p">{</span>
    <span class="n">Success</span><span class="p">,</span>
    <span class="n">InvalidId</span><span class="p">,</span>
    <span class="n">NotFound</span><span class="p">,</span>
    <span class="n">AlreadyInactive</span><span class="p">,</span>
    <span class="n">InfraError</span>
<span class="p">}</span>

<span class="k">public</span> <span class="n">DeactivateUserResult</span> <span class="nf">DeactivateUser</span><span class="p">(</span><span class="kt">string</span> <span class="n">inputId</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(!</span><span class="kt">int</span><span class="p">.</span><span class="nf">TryParse</span><span class="p">(</span><span class="n">inputId</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">id</span><span class="p">))</span> <span class="k">return</span> <span class="n">DeactivateUserResult</span><span class="p">.</span><span class="n">InvalidId</span><span class="p">;</span>

    <span class="n">User</span><span class="p">?</span> <span class="n">user</span><span class="p">;</span>
    <span class="k">try</span>
    <span class="p">{</span>
        <span class="n">user</span> <span class="p">=</span> <span class="n">_repo</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// logging omitted for brevity</span>
        <span class="k">return</span> <span class="n">DeactivateUserResult</span><span class="p">.</span><span class="n">InfraError</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">user</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">DeactivateUserResult</span><span class="p">.</span><span class="n">NotFound</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(!</span><span class="n">user</span><span class="p">.</span><span class="n">IsActive</span><span class="p">)</span> <span class="k">return</span> <span class="n">DeactivateUserResult</span><span class="p">.</span><span class="n">AlreadyInactive</span><span class="p">;</span>

    <span class="c1">// assuming an ORM that requires mutation</span>
    <span class="n">user</span><span class="p">.</span><span class="n">IsActive</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
    <span class="k">try</span>
    <span class="p">{</span>
        <span class="n">_repo</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="n">user</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// logging omitted for brevity</span>
        <span class="k">return</span> <span class="n">DeactivateUserResult</span><span class="p">.</span><span class="n">InfraError</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">DeactivateUserResult</span><span class="p">.</span><span class="n">Success</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>An enum gives you a status, but not a success payload. If you need to return data on success, you end up adding an <code class="language-plaintext highlighter-rouge">out</code> parameter, returning a tuple, or introducing a wrapper type.
Conventions are easy to violate: nothing stops you from returning <code class="language-plaintext highlighter-rouge">(user: null, error: null)</code> or populating both.</p>

<blockquote>
  <p><strong>Note:</strong> You can fix the “invalid combinations” problem with an <code class="language-plaintext highlighter-rouge">OperationStatus</code> hierarchy (e.g., <code class="language-plaintext highlighter-rouge">OperationSuccess</code> / <code class="language-plaintext highlighter-rouge">OperationFailure</code>) or a private constructor plus <code class="language-plaintext highlighter-rouge">Success(...)</code>/<code class="language-plaintext highlighter-rouge">Failure(...)</code> factories. That helps, but you still need good composition to avoid “check the status after every step.”</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">Result</code> packages these conventions + combinators into a reusable shape (<code class="language-plaintext highlighter-rouge">Ok(...)</code>/<code class="language-plaintext highlighter-rouge">Fail(...)</code>, <code class="language-plaintext highlighter-rouge">Map</code>/<code class="language-plaintext highlighter-rouge">Bind</code>).</p>

<h4 id="the-solution-the-result-monad">The solution: the <code class="language-plaintext highlighter-rouge">Result</code> monad</h4>

<p><code class="language-plaintext highlighter-rouge">Result</code> returns <em>expected</em> failure as data (as long as your steps return <code class="language-plaintext highlighter-rouge">Result</code> rather than throwing), instead of using an <code class="language-plaintext highlighter-rouge">exception</code> jump. Unexpected <code class="language-plaintext highlighter-rouge">exceptions</code> still escape.</p>

<p>Now each step either produces the next value or propagates the first <code class="language-plaintext highlighter-rouge">Fail(...)</code>. This is often described as <strong>short-circuiting</strong>.<sup id="fnref:shortcircuit:1" role="doc-noteref"><a href="#fn:shortcircuit" class="footnote" rel="footnote">2</a></sup></p>

<p>Non-LINQ syntax (plain method chaining):</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="n">result</span> <span class="p">=</span>
    <span class="nf">ParseId</span><span class="p">(</span><span class="n">inputId</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="n">FindUser</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="n">DeactivateDecision</span><span class="p">);</span>
</code></pre></div></div>

<blockquote>
  <p>In <code class="language-plaintext highlighter-rouge">C#</code>, <code class="language-plaintext highlighter-rouge">string?</code> is a nullable <em>reference</em> annotation; <code class="language-plaintext highlighter-rouge">int?</code> is <code class="language-plaintext highlighter-rouge">Nullable&lt;int&gt;</code>. In Rust, <code class="language-plaintext highlighter-rouge">?</code> is an early-return operator for <code class="language-plaintext highlighter-rouge">Result</code>/<code class="language-plaintext highlighter-rouge">Option</code> (and other types that implement the <code class="language-plaintext highlighter-rouge">Try</code> pattern).</p>
</blockquote>

<p>LINQ query syntax (<code class="language-plaintext highlighter-rouge">Select</code>/<code class="language-plaintext highlighter-rouge">SelectMany</code>) is in the appendix.</p>

<h5 id="procedural-code-detour">Procedural code detour</h5>
<details>
  <summary>Procedural code detour (collapsed)</summary>

  <blockquote>
    <p>The detour provides procedural context for readers new to FP; it’s optional.</p>
  </blockquote>

  <p>If <code class="language-plaintext highlighter-rouge">ParseId</code> returns <code class="language-plaintext highlighter-rouge">Result.Fail(...)</code>, the pipeline short-circuits; if <code class="language-plaintext highlighter-rouge">ParseId</code> returns <code class="language-plaintext highlighter-rouge">Result.Ok(...)</code>, the pipeline continues.</p>

  <p>The key is that the steps (<code class="language-plaintext highlighter-rouge">ParseId</code>, <code class="language-plaintext highlighter-rouge">FindUser</code>, <code class="language-plaintext highlighter-rouge">DeactivateDecision</code>) don’t know they’re in a pipeline. For example, <code class="language-plaintext highlighter-rouge">FindUser</code> simply accepts an <code class="language-plaintext highlighter-rouge">int</code> and returns either <code class="language-plaintext highlighter-rouge">Result.Ok(...)</code> or <code class="language-plaintext highlighter-rouge">Result.Fail(...)</code>.</p>

  <p>Who decides? <code class="language-plaintext highlighter-rouge">Bind</code>.</p>

  <p>After <code class="language-plaintext highlighter-rouge">ParseId</code> runs, the <code class="language-plaintext highlighter-rouge">Result</code> is in one of two states:</p>

  <ul>
    <li>Ok: contains a success value</li>
    <li>Fail: contains an error</li>
  </ul>

  <p>Internally, the <code class="language-plaintext highlighter-rouge">Result</code> type stores a flag and either a value or an error. <code class="language-plaintext highlighter-rouge">Result.Ok(...)</code> and <code class="language-plaintext highlighter-rouge">Result.Fail(...)</code> are factories.</p>

  <p>Implementation-wise, your <code class="language-plaintext highlighter-rouge">Result</code> type stores some internal flag/tag (often something like <code class="language-plaintext highlighter-rouge">isSuccess</code>) plus either the success value or the error. <code class="language-plaintext highlighter-rouge">Result.Ok(...)</code> and <code class="language-plaintext highlighter-rouge">Result.Fail(...)</code> are just factory methods that create an instance of the <code class="language-plaintext highlighter-rouge">Result</code> class with that internal flag/tag set appropriately.</p>

  <p>Because <em>both</em> success and failure are represented by the same <code class="language-plaintext highlighter-rouge">Result</code> type, you can always call <code class="language-plaintext highlighter-rouge">Bind</code> next, on failure or success.</p>

  <p><code class="language-plaintext highlighter-rouge">Bind</code> then does one of two things:</p>

  <ul>
    <li>If the current <code class="language-plaintext highlighter-rouge">Result</code> is <code class="language-plaintext highlighter-rouge">Ok</code>, <code class="language-plaintext highlighter-rouge">Bind</code> calls the next step (e.g., <code class="language-plaintext highlighter-rouge">FindUser</code>, passed in as a function/delegate) and returns <em>that</em> step’s <code class="language-plaintext highlighter-rouge">Result</code>.</li>
    <li>If the current <code class="language-plaintext highlighter-rouge">Result</code> is <code class="language-plaintext highlighter-rouge">Fail</code>, <code class="language-plaintext highlighter-rouge">Bind</code> skips the next step and returns the existing failure unchanged. It ignores the passed in step.</li>
  </ul>

  <p>Finally, chaining works because every step returns a <code class="language-plaintext highlighter-rouge">Result</code>. This keeps the shape consistent so you can keep calling <code class="language-plaintext highlighter-rouge">Bind</code>. If a step returned an unrelated type (or <code class="language-plaintext highlighter-rouge">void</code>), the chain would break.</p>

  <p>The key point: <code class="language-plaintext highlighter-rouge">Result</code> handles sequencing; steps just return a <code class="language-plaintext highlighter-rouge">Result</code>, and <code class="language-plaintext highlighter-rouge">Bind</code> ensures the next step runs after an Ok.</p>

</details>

<h4 id="how-to-get-out-of-a-result-with-match">How to get out of a Result with Match</h4>

<p>Translating between layers often means turning a <code class="language-plaintext highlighter-rouge">Result</code> into a different output shape via <code class="language-plaintext highlighter-rouge">Match</code>. In practice you’ll often also want <code class="language-plaintext highlighter-rouge">MapError</code> (or <code class="language-plaintext highlighter-rouge">BindError</code>) to translate error types between layers without ending the pipeline.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Result</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="n">infraResult</span> <span class="p">=</span> <span class="nf">ParseId</span><span class="p">(</span><span class="n">inputIdFromRequest</span><span class="p">);</span>

<span class="kt">string</span> <span class="n">response</span> <span class="p">=</span> <span class="n">infraResult</span><span class="p">.</span><span class="nf">Match</span><span class="p">(</span>
        <span class="n">ok</span><span class="p">:</span> <span class="n">id</span> <span class="p">=&gt;</span> <span class="s">$"OK: </span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
        <span class="n">err</span><span class="p">:</span> <span class="n">e</span> <span class="p">=&gt;</span> <span class="s">$"Bad request: </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Code</span><span class="p">}</span><span class="s"> - </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
</code></pre></div></div>

<p>At boundaries (HTTP/CLI/public APIs), you typically translate into a DTO/status/<code class="language-plaintext highlighter-rouge">ProblemDetails</code>/exit code.</p>

<p><strong>IMPORTANT!</strong> C# doesn’t force you to handle a returned <code class="language-plaintext highlighter-rouge">Result</code>. Use an analyzer.<sup id="fnref:unused-result" role="doc-noteref"><a href="#fn:unused-result" class="footnote" rel="footnote">7</a></sup></p>

<h3 id="a-tiny-result-implementation">A tiny <code class="language-plaintext highlighter-rouge">Result</code> implementation</h3>

<blockquote>
  <p>If you’re curious, try implementing <code class="language-plaintext highlighter-rouge">Result</code> yourself first.</p>
</blockquote>

<p>This teaching implementation isn’t production-ready (use <a href="https://github.com/louthy/language-ext">LanguageExt</a>, <a href="https://github.com/pimbrouwers/Danom">Danom</a>, or <em>CSharpFunctionalExtensions</em>) and is intentionally minimalist and unsafe around <code class="language-plaintext highlighter-rouge">default</code>/null. It stores <code class="language-plaintext highlighter-rouge">default</code> in the unused slot (don’t read it), doesn’t prevent <code class="language-plaintext highlighter-rouge">Ok(null)</code> / <code class="language-plaintext highlighter-rouge">Fail(null)</code>, doesn’t guard against “returning null” from <code class="language-plaintext highlighter-rouge">Bind</code>, has no <code class="language-plaintext highlighter-rouge">async</code> support, no <code class="language-plaintext highlighter-rouge">Equals</code>/<code class="language-plaintext highlighter-rouge">GetHashCode</code>, and doesn’t catch <code class="language-plaintext highlighter-rouge">exceptions</code> – to keep the focus on the core shape.</p>

<p>Performance note: this sample uses a <code class="language-plaintext highlighter-rouge">class</code> for clarity, but in high-throughput / low-allocation scenarios you’d typically implement <code class="language-plaintext highlighter-rouge">Result</code> as a <code class="language-plaintext highlighter-rouge">readonly struct</code> (or <code class="language-plaintext highlighter-rouge">record struct</code>) to reduce GC pressure (often alongside careful API design to avoid copying).</p>

<p>Here’s the implementation for <code class="language-plaintext highlighter-rouge">Result</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">Result</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">TSuccess</span> <span class="n">_value</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">TError</span> <span class="n">_error</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="kt">bool</span> <span class="n">_isSuccess</span><span class="p">;</span>

    <span class="k">private</span> <span class="nf">Result</span><span class="p">(</span><span class="n">TSuccess</span> <span class="k">value</span><span class="p">,</span> <span class="n">TError</span> <span class="n">error</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">isSuccess</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_isSuccess</span> <span class="p">=</span> <span class="n">isSuccess</span><span class="p">;</span>
        <span class="n">_value</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
        <span class="n">_error</span> <span class="p">=</span> <span class="n">error</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Unit</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">TSuccess</span> <span class="k">value</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;(</span>
            <span class="k">value</span><span class="p">,</span>
            <span class="k">default</span><span class="p">,</span>
            <span class="k">true</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="nf">Fail</span><span class="p">(</span><span class="n">TError</span> <span class="n">error</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;(</span>
            <span class="k">default</span><span class="p">,</span>
            <span class="n">error</span><span class="p">,</span>
            <span class="k">false</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="n">Map</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">U</span><span class="p">&gt;</span> <span class="n">f</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_isSuccess</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;.</span><span class="nf">Ok</span><span class="p">(</span><span class="nf">f</span><span class="p">(</span><span class="n">_value</span><span class="p">));</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;.</span><span class="nf">Fail</span><span class="p">(</span><span class="n">_error</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="n">Bind</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;&gt;</span> <span class="n">f</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_isSuccess</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="nf">f</span><span class="p">(</span><span class="n">_value</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;.</span><span class="nf">Fail</span><span class="p">(</span><span class="n">_error</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">TResult</span> <span class="n">Match</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">ok</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TError</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">err</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_isSuccess</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="nf">ok</span><span class="p">(</span><span class="n">_value</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="nf">err</span><span class="p">(</span><span class="n">_error</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>Where is <code class="language-plaintext highlighter-rouge">Result.Unit(...)</code>? In monad terms: for <code class="language-plaintext highlighter-rouge">Result&lt;_, TError&gt;</code>, <code class="language-plaintext highlighter-rouge">Ok(...)</code> is <strong>return/pure</strong> (the monadic “unit”). <code class="language-plaintext highlighter-rouge">Fail(...)</code> is the constructor for the error case. Practically, <code class="language-plaintext highlighter-rouge">Result.Unit(...)</code> = <code class="language-plaintext highlighter-rouge">Result.Ok(...)</code>.</p>
</blockquote>

<h3 id="where-result-fits-and-where-it-doesnt">Where <code class="language-plaintext highlighter-rouge">Result</code> fits (and where it doesn’t)</h3>

<p>Rule of thumb: <code class="language-plaintext highlighter-rouge">T?</code> for something that could be null, <code class="language-plaintext highlighter-rouge">Maybe&lt;T&gt;</code> for expected absence (no reason), <code class="language-plaintext highlighter-rouge">Result&lt;TSuccess, TError&gt;</code> for expected failures you’ll handle, and <code class="language-plaintext highlighter-rouge">exceptions</code> often for bugs or unrecoverable failures.<sup id="fnref:always-valid" role="doc-noteref"><a href="#fn:always-valid" class="footnote" rel="footnote">8</a></sup>
Also: model only the failure detail callers can act on; if <code class="language-plaintext highlighter-rouge">TError</code> is part of a public API, keep it small and stable.</p>

<p><strong>Prefer <code class="language-plaintext highlighter-rouge">Result</code> when:</strong></p>

<ul>
  <li><strong>Failure is expected and recoverable:</strong> validation/business rules, not found, auth failures, parsing user input.</li>
  <li><strong>You want refactor pressure (and sometimes exhaustiveness):</strong> refactors become visible because failure is in the return type.</li>
  <li><strong>Failure is routine / on hot paths:</strong> avoid throwing for control flow; prefer <code class="language-plaintext highlighter-rouge">TryParse</code>/<code class="language-plaintext highlighter-rouge">Result</code>-style returns.</li>
</ul>

<p><strong>Prefer <code class="language-plaintext highlighter-rouge">exceptions</code> (or other types) when:</strong></p>

<ul>
  <li><strong>It’s a bug / broken invariant:</strong> violated preconditions, “impossible states” → often <code class="language-plaintext highlighter-rouge">exceptions</code> (e.g., <code class="language-plaintext highlighter-rouge">ArgumentNullException</code>).</li>
  <li><strong>Continuing is pointless / you need stack traces:</strong> misconfiguration, out-of-memory, “dead end” aborts → often <code class="language-plaintext highlighter-rouge">exceptions</code> / short-circuit, array out of bounds.</li>
  <li><strong>You need accumulation:</strong> <code class="language-plaintext highlighter-rouge">Bind</code> is short-circuiting; use <code class="language-plaintext highlighter-rouge">Validation&lt;T&gt;</code>/applicatives (or a combine API) for independent validations.</li>
</ul>

<h3 id="putting-it-together">Putting it together</h3>

<p>Eventually you turn a <code class="language-plaintext highlighter-rouge">Result</code> into something your caller understands (HTTP response, CLI exit code, UI state, etc.). A boundary (the “edge” of the system) is a good place to <code class="language-plaintext highlighter-rouge">Match</code>.</p>

<blockquote>
  <p><strong>Boundary/Application Layer:</strong> the boundary (or edge) is where your program interacts with something you don’t fully control (inputs, networks, storage, other systems).</p>
</blockquote>

<p>See <code class="language-plaintext highlighter-rouge">HandleDeactivateRequest</code> below for a concrete example.</p>

<h4 id="example-deactivate-a-user">Example: deactivate a user</h4>
<p>We want to deactivate a user given a user’s <code class="language-plaintext highlighter-rouge">id</code> from an HTTP request (received as a <strong>string</strong>, parsed to an <code class="language-plaintext highlighter-rouge">int</code>).<sup id="fnref:id" role="doc-noteref"><a href="#fn:id" class="footnote" rel="footnote">9</a></sup></p>

<p>This HTTP API/user service example is just a convenient boundary; the same idea applies to CLIs, jobs, message handlers, UI workflows, etc.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">User</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsActive</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">UserService</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IUserRepo</span> <span class="n">_repo</span><span class="p">;</span>
    <span class="k">public</span> <span class="nf">UserService</span><span class="p">(</span><span class="n">IUserRepo</span> <span class="n">repo</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_repo</span> <span class="p">=</span> <span class="n">repo</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="nf">DeactivateUser</span><span class="p">(</span><span class="kt">string</span> <span class="n">inputId</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// Workflow composition; the write happens at the boundary (see `HandleDeactivateRequest`).</span>
        <span class="k">return</span> <span class="nf">ParseId</span><span class="p">(</span><span class="n">inputId</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="n">FindUser</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="n">DeactivateDecision</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="nf">HandleDeactivateRequest</span><span class="p">(</span><span class="kt">string</span> <span class="n">inputId</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="n">result</span> <span class="p">=</span> <span class="nf">DeactivateUser</span><span class="p">(</span><span class="n">inputId</span><span class="p">);</span>

        <span class="k">return</span> <span class="n">result</span><span class="p">.</span><span class="nf">Match</span><span class="p">(</span>
            <span class="n">ok</span><span class="p">:</span> <span class="n">user</span> <span class="p">=&gt;</span>
            <span class="p">{</span>
                <span class="c1">// If this throws, assume middleware/global handlers translate + log infra failures.</span>
                <span class="n">_repo</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="n">user</span><span class="p">);</span>
                <span class="k">return</span> <span class="s">"User deactivated"</span><span class="p">;</span>
            <span class="p">},</span>
            <span class="n">err</span><span class="p">:</span> <span class="n">e</span> <span class="p">=&gt;</span> <span class="s">$"Deactivate failed: </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Code</span><span class="p">}</span><span class="s"> - </span><span class="p">{</span><span class="n">e</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">static</span> <span class="n">Result</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="nf">ParseId</span><span class="p">(</span><span class="kt">string</span> <span class="n">inputId</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="kt">int</span><span class="p">.</span><span class="nf">TryParse</span><span class="p">(</span><span class="n">inputId</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">id</span><span class="p">)</span>
            <span class="p">?</span> <span class="n">Result</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Ok</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
            <span class="p">:</span> <span class="n">Result</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Fail</span><span class="p">(</span><span class="k">new</span> <span class="nf">Error</span><span class="p">(</span><span class="s">"Parse"</span><span class="p">,</span> <span class="s">"Invalid ID format"</span><span class="p">));</span>

    <span class="k">private</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="nf">FindUser</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">user</span> <span class="p">=</span> <span class="n">_repo</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">user</span> <span class="k">is</span> <span class="k">null</span>
            <span class="p">?</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Fail</span><span class="p">(</span><span class="k">new</span> <span class="nf">Error</span><span class="p">(</span><span class="s">"NotFound"</span><span class="p">,</span> <span class="s">$"User </span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="s"> not found"</span><span class="p">))</span>
            <span class="p">:</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Ok</span><span class="p">(</span><span class="n">user</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">static</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;</span> <span class="nf">DeactivateDecision</span><span class="p">(</span><span class="n">User</span> <span class="n">user</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(!</span><span class="n">user</span><span class="p">.</span><span class="n">IsActive</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Fail</span><span class="p">(</span><span class="k">new</span> <span class="nf">Error</span><span class="p">(</span><span class="s">"Domain"</span><span class="p">,</span> <span class="s">"User is already inactive"</span><span class="p">));</span>

        <span class="n">user</span><span class="p">.</span><span class="n">IsActive</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">User</span><span class="p">,</span> <span class="n">Error</span><span class="p">&gt;.</span><span class="nf">Ok</span><span class="p">(</span><span class="n">user</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This computes <code class="language-plaintext highlighter-rouge">Result&lt;User, Error&gt;</code> internally, then <code class="language-plaintext highlighter-rouge">Match</code>es at the boundary (<code class="language-plaintext highlighter-rouge">HandleDeactivateRequest</code>) to produce the caller-facing output. In a real HTTP endpoint, you’d return <code class="language-plaintext highlighter-rouge">IActionResult</code>/<code class="language-plaintext highlighter-rouge">IResult</code> (not a <code class="language-plaintext highlighter-rouge">string</code>) and map <code class="language-plaintext highlighter-rouge">Error</code> to <code class="language-plaintext highlighter-rouge">ProblemDetails</code>/status codes.</p>

<h4 id="why-is-_reposaveuser-inside-match">Why is <code class="language-plaintext highlighter-rouge">_repo.Save(user)</code> inside <code class="language-plaintext highlighter-rouge">Match</code>?</h4>

<p><code class="language-plaintext highlighter-rouge">Save</code> is <code class="language-plaintext highlighter-rouge">I/O</code>. It can fail with domain-relevant outcomes (e.g., uniqueness conflicts) and unexpected infrastructure <code class="language-plaintext highlighter-rouge">exceptions</code> (timeouts, outages). If you want conflicts to be “expected failures,” catch and translate the specific <code class="language-plaintext highlighter-rouge">exception</code> into a <code class="language-plaintext highlighter-rouge">Result</code> error; otherwise let truly unexpected <code class="language-plaintext highlighter-rouge">exceptions</code> bubble to boundary handlers.</p>

<h3 id="why-serializing-result-directly-can-be-awkward">Why serializing <code class="language-plaintext highlighter-rouge">Result</code> directly can be awkward</h3>

<p>In <strong>public contracts</strong>, serializing <code class="language-plaintext highlighter-rouge">Result</code> tends to leak an internal control-flow wrapper into your schema. Prefer <code class="language-plaintext highlighter-rouge">Match</code> into a DTO / HTTP status / <code class="language-plaintext highlighter-rouge">ProblemDetails</code> (unless using a custom converter).</p>

<p>Some <code class="language-plaintext highlighter-rouge">Result</code> types could expose public <code class="language-plaintext highlighter-rouge">Value</code>/<code class="language-plaintext highlighter-rouge">Error</code>/flags, which can make serialization even worse. If you serialize a <code class="language-plaintext highlighter-rouge">Result</code>-shaped class directly, you can end up with confusing “wrapper JSON” like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"isSuccess"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
  </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
  </span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DbError"</span><span class="p">,</span><span class="w"> </span><span class="nl">"details"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="err">/*</span><span class="w"> </span><span class="err">...</span><span class="w"> </span><span class="err">*/</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Now your public API couples clients to an internal control-flow wrapper and can leak internal shapes. Clients have to interpret <code class="language-plaintext highlighter-rouge">isSuccess</code> + <code class="language-plaintext highlighter-rouge">value/error</code> <em>and</em> your HTTP status code. What if <code class="language-plaintext highlighter-rouge">isSuccess</code> is true and <code class="language-plaintext highlighter-rouge">error</code> contains an error? It’s a weird situation.</p>

<h3 id="note-on-async-taskresult-nesting-is-where-people-get-stuck">Note on async: <code class="language-plaintext highlighter-rouge">Task&lt;Result&lt;...&gt;&gt;</code> nesting is where people get stuck</h3>

<p>Once you mix <code class="language-plaintext highlighter-rouge">Task</code> and <code class="language-plaintext highlighter-rouge">Result</code>, you quickly end up with <code class="language-plaintext highlighter-rouge">Task&lt;Result&lt;TSuccess, TError&gt;&gt;</code>, and plain LINQ query syntax doesn’t compose that shape out of the box (because <code class="language-plaintext highlighter-rouge">Task&lt;Result&lt;...&gt;&gt;</code> doesn’t have the right <code class="language-plaintext highlighter-rouge">SelectMany</code>).</p>

<p>In practice you either:</p>

<ul>
  <li>add async-aware combinators like <code class="language-plaintext highlighter-rouge">BindAsync</code>/<code class="language-plaintext highlighter-rouge">MapAsync</code>, or</li>
  <li>add LINQ helpers like <code class="language-plaintext highlighter-rouge">SelectManyAsync</code> to compose <code class="language-plaintext highlighter-rouge">Task&lt;Result&lt;...&gt;&gt;</code> without “await soup”.</li>
</ul>

<p>Rather than reimplement those helpers here, use a library that provides them.</p>

<h3 id="recap">Recap</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Result&lt;TSuccess, TError&gt;</code> makes expected failure explicit and composable.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">Bind</code> for short-circuiting pipelines; <code class="language-plaintext highlighter-rouge">Match</code> to produce caller-facing output.</li>
  <li>For public contracts, unwrap <code class="language-plaintext highlighter-rouge">Result</code> into DTOs (rather than serializing it). Decide where you catch/translate <code class="language-plaintext highlighter-rouge">exceptions</code>.</li>
</ul>

<h3 id="appendix-linq-query-syntax-selectselectmany">Appendix: LINQ query syntax (<code class="language-plaintext highlighter-rouge">Select</code>/<code class="language-plaintext highlighter-rouge">SelectMany</code>)</h3>
<p>If you want <code class="language-plaintext highlighter-rouge">from</code>/<code class="language-plaintext highlighter-rouge">from</code>/<code class="language-plaintext highlighter-rouge">select</code> query syntax to compile, add these extension methods (or add them directly to <code class="language-plaintext highlighter-rouge">Result</code>). Other keywords like <code class="language-plaintext highlighter-rouge">where</code> require additional methods.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ResultLinqExtensions</span>
<span class="p">{</span>
    <span class="c1">// Required for `select`</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="n">Select</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;(</span>
        <span class="k">this</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="n">result</span><span class="p">,</span>
        <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">U</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">result</span><span class="p">.</span><span class="nf">Map</span><span class="p">(</span><span class="n">selector</span><span class="p">);</span>

    <span class="c1">// Required for multiple `from` clauses</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">V</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="n">SelectMany</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">U</span><span class="p">,</span> <span class="n">V</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;(</span>
        <span class="k">this</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;</span> <span class="n">result</span><span class="p">,</span>
        <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">U</span><span class="p">,</span> <span class="n">TError</span><span class="p">&gt;&gt;</span> <span class="n">bind</span><span class="p">,</span>
        <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSuccess</span><span class="p">,</span> <span class="n">U</span><span class="p">,</span> <span class="n">V</span><span class="p">&gt;</span> <span class="n">project</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">result</span><span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="n">val</span> <span class="p">=&gt;</span> <span class="nf">bind</span><span class="p">(</span><span class="n">val</span><span class="p">).</span><span class="nf">Map</span><span class="p">(</span><span class="n">next</span> <span class="p">=&gt;</span> <span class="nf">project</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">next</span><span class="p">)));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Part 3 coming soon.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:result-monad-precise" role="doc-endnote">
      <p>More precisely: for a fixed error type, <code class="language-plaintext highlighter-rouge">Result&lt;_, TError&gt;</code> (like right-biased <code class="language-plaintext highlighter-rouge">Either</code>) forms a monad. See <a href="https://www.scala-exercises.org/cats/either">Cats: Either</a>. In the basic <code class="language-plaintext highlighter-rouge">Bind</code> form, the error type stays the same throughout the chain; if you need to change it, you typically translate it explicitly (e.g., <code class="language-plaintext highlighter-rouge">MapError</code>) or widen it to a common error type. <a href="#fnref:result-monad-precise" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:shortcircuit" role="doc-endnote">
      <p>“Short-circuit” here: after the first failure, later steps aren’t called; the failure value just propagates. <a href="#fnref:shortcircuit" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:shortcircuit:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:checked-exceptions" role="doc-endnote">
      <p>Java has <em>checked exceptions</em> (<code class="language-plaintext highlighter-rouge">throws</code> forces callers to catch/declare them), but unchecked exceptions still exist. C# has no checked exceptions, so “might throw” usually isn’t in the signature (unless documented). <a href="#fnref:checked-exceptions" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:checked-exceptions:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:accumulation" role="doc-endnote">
      <p><code class="language-plaintext highlighter-rouge">Bind</code> is sequential and short-circuiting. If you need to accumulate independent validation errors, prefer a <code class="language-plaintext highlighter-rouge">Validation&lt;T&gt;</code>/applicative (or a dedicated <code class="language-plaintext highlighter-rouge">Combine</code> API). If you have several first-class outcomes, a union/tagged type is often a better model than forcing “success vs error”. <a href="#fnref:accumulation" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:either" role="doc-endnote">
      <p>Closest analogue in FP is usually <code class="language-plaintext highlighter-rouge">Either</code> (often Left=error, Right=success, but conventions vary). <a href="#fnref:either" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:immutability" role="doc-endnote">
      <p>Mutation could make pipelines harder to reason about and test. Prefer immutability (<code class="language-plaintext highlighter-rouge">record</code> + <code class="language-plaintext highlighter-rouge">init</code>, or return a new value); I mutate here to keep focus on <code class="language-plaintext highlighter-rouge">Result</code> mechanics. <a href="#fnref:immutability" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unused-result" role="doc-endnote">
      <p>C# lets you ignore return values, so a <code class="language-plaintext highlighter-rouge">Result</code> can be silently dropped. Use a Roslyn analyzer to flag unused <code class="language-plaintext highlighter-rouge">Result</code>s. <a href="#fnref:unused-result" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:always-valid" role="doc-endnote">
      <p>Vladimir Khorikov, <a href="https://enterprisecraftsmanship.com/posts/always-valid-vs-not-always-valid-domain-model/">Always valid vs not always valid domain model</a>. <a href="#fnref:always-valid" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:id" role="doc-endnote">
      <p>In real systems, prefer a strongly typed ID (e.g., <code class="language-plaintext highlighter-rouge">UserId</code>) over primitives. Here I keep it simple: <code class="language-plaintext highlighter-rouge">string</code> at the boundary, parse to <code class="language-plaintext highlighter-rouge">int</code>, focus on <code class="language-plaintext highlighter-rouge">Result</code>. <a href="#fnref:id" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[Build a small Result type in C# and use `Map`/`Bind`/`Match` to compose short-circuiting workflows with explicit errors.]]></summary></entry><entry><title type="html">List is a monad (part 1)</title><link href="https://alexyorke.github.io/2025/06/29/list-is-a-monad/" rel="alternate" type="text/html" title="List is a monad (part 1)" /><published>2025-06-29T00:00:00+00:00</published><updated>2025-06-29T00:00:00+00:00</updated><id>https://alexyorke.github.io/2025/06/29/a-list-is-a-monad</id><content type="html" xml:base="https://alexyorke.github.io/2025/06/29/list-is-a-monad/"><![CDATA[<p>Note July 6th 2025: this post’s original title was “A list is a monad”. It has been changed to “List is a monad”.</p>

<p>Note Sept 13 2025: this post has been revised based on the feedback from <a href="https://news.ycombinator.com/item?id=44414965">the Hacker News discussion</a>.</p>

<p>The term “monad” is often invoked when describing patterns in functional programming. At the heart of monadic programming is sequencing computations, so each step can depend on the previous one while the monad threads context.</p>

<p>You may erroneously think all monads are containers, or burritos, or boxes. The <strong>simplest</strong> of monads can be <a href="https://en.wikipedia.org/wiki/Idealization_%28philosophy_of_science%29">idealized</a> as a <strong>container</strong> (albeit a <a href="https://byorgey.github.io/blog/posts/2025/06/16/monads-are-not-burritos.html">flawed metaphor</a>). Monads are much more than just containers, and there isn’t the-one-and-only monad; instead it’s better to think about them as a <strong>programming pattern, recipe, factoring out control flow, or, in some cases, a deferred computation</strong>. It depends on which monad you’re talking about.</p>

<p>From a teaching perspective, to get the concept for what a monad is, we will start with the simplest of monads which will feel a lot like just a container but with some composable aspects. This provides the infrastructure to understand more complex monads later on.</p>

<h2 id="list-map--flatmap-in-practice">List: Map &amp; flatMap in Practice</h2>

<p>To an OOP developer, monadic types (<code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code>) might look just like generics. It’s a typical pitfall to think “we have generics, so we have monads,” which isn’t true by itself. Monads do usually involve generic types, but they <strong>require specific operations (<code class="language-plaintext highlighter-rouge">Unit</code> and <code class="language-plaintext highlighter-rouge">flatMap</code>) and the three monad laws on those types to ensure uniform behavior.</strong> <strong>This is key</strong> and is fundamental to working with monads.</p>

<p>A good example of a monad is <code class="language-plaintext highlighter-rouge">List</code>. You’re likely very familiar with lists and working with lists.</p>

<p>The monad <code class="language-plaintext highlighter-rouge">Map</code> operation is responsible for:</p>

<ul>
  <li>
    <p><strong>Applying your function.</strong> For <code class="language-plaintext highlighter-rouge">List</code>, <code class="language-plaintext highlighter-rouge">Map</code> runs <code class="language-plaintext highlighter-rouge">f</code> (a function) on <em>every</em> element. For example, let’s define <code class="language-plaintext highlighter-rouge">f</code> as <code class="language-plaintext highlighter-rouge">f(x) = x + 1</code>. The list <code class="language-plaintext highlighter-rouge">[0,1,2,3]</code> becomes <code class="language-plaintext highlighter-rouge">[1,2,3,4]</code>. If the list doesn’t have any elements, then <code class="language-plaintext highlighter-rouge">Map</code> doesn’t call <code class="language-plaintext highlighter-rouge">f</code>. <code class="language-plaintext highlighter-rouge">f</code> doesn’t need to worry about that. Also, <code class="language-plaintext highlighter-rouge">f</code> doesn’t care if it’s <code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code>; all <code class="language-plaintext highlighter-rouge">f</code> is, is just <code class="language-plaintext highlighter-rouge">f(x) = x + 1</code>. <code class="language-plaintext highlighter-rouge">Map</code> is responsible for running it.</p>
  </li>
  <li>
    <p><strong>Managing sequencing and combination.</strong> The list context concatenates all results into one list (<code class="language-plaintext highlighter-rouge">Map</code> does <strong>not</strong> flatten any nested lists, <code class="language-plaintext highlighter-rouge">flatMap</code> is responsible for this). We don’t need to manually re-add elements via <code class="language-plaintext highlighter-rouge">Add</code> or otherwise manage the collection ourselves.</p>
  </li>
</ul>

<p>Notice that the monad in this case <code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code> is responsible for running <code class="language-plaintext highlighter-rouge">f</code>. This shift means your business logic stays <strong>declarative</strong> and <strong>composable</strong>, you describe <em>what</em> happens to a single value, and the monad describes <em>how</em> and <em>when</em> it happens.</p>

<p>This is different from object-oriented and procedural programming because in those paradigms, if you want to process data, it is your responsibility to understand how to apply the function to your data. We have to use different control constructs to handle different types of data, and we’re also responsible for the “how”:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">string</span> <span class="nf">f</span><span class="p">(</span><span class="kt">string</span> <span class="n">input</span><span class="p">)</span> <span class="p">{</span>  
  <span class="k">return</span> <span class="n">input</span> <span class="p">+</span> <span class="s">" -appended text"</span><span class="p">;</span>  
<span class="p">}</span>

<span class="c1">// 1. List&lt;string&gt;: you must foreach and build a new list</span>

<span class="kt">var</span> <span class="n">fruits</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="p">{</span>  
  <span class="s">"apple"</span><span class="p">,</span>  
  <span class="s">"banana"</span><span class="p">,</span>  
  <span class="s">"cherry"</span>  
<span class="p">};</span>

<span class="kt">var</span> <span class="n">newFruits</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;();</span>

<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">fruit</span> <span class="k">in</span> <span class="n">fruits</span><span class="p">)</span>  
<span class="p">{</span>  
  <span class="n">newFruits</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="nf">f</span><span class="p">(</span><span class="n">fruit</span><span class="p">));</span>  
<span class="p">}</span>

<span class="c1">// 2. Single string: you must check for null first, then concatenate</span>

<span class="kt">string</span> <span class="n">userInput</span> <span class="p">=</span> <span class="nf">GetUserInput</span><span class="p">();</span> <span class="c1">// could be null</span>

<span class="k">if</span> <span class="p">(</span><span class="n">userInput</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>  
<span class="p">{</span>  
  <span class="n">userInput</span> <span class="p">=</span> <span class="nf">f</span><span class="p">(</span><span class="n">userInput</span><span class="p">);</span>  
<span class="p">}</span>

<span class="c1">// userInput could still be null here, or it could be the concatenated result</span>

<span class="c1">// 3. Dictionary&lt;string, string&gt;: you must know it’s key/value pairs</span>

<span class="kt">var</span> <span class="n">dict</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span>  
<span class="p">{</span>  
  <span class="p">[</span><span class="s">"a"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"alpha"</span><span class="p">,</span>  
  <span class="p">[</span><span class="s">"b"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"beta"</span><span class="p">,</span>  
  <span class="p">[</span><span class="s">"c"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"gamma"</span>  
<span class="p">};</span>

<span class="c1">// can’t modify while iterating, so capture keys first  </span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">key</span> <span class="k">in</span> <span class="n">dict</span><span class="p">.</span><span class="n">Keys</span><span class="p">.</span><span class="nf">ToList</span><span class="p">())</span>  
<span class="p">{</span>  
  <span class="n">dict</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="p">=</span> <span class="nf">f</span><span class="p">(</span><span class="n">dict</span><span class="p">[</span><span class="n">key</span><span class="p">]);</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>In these examples, we are forced to know <strong>how</strong> to update each structure procedurally. For a <code class="language-plaintext highlighter-rouge">List</code>, we have to call <code class="language-plaintext highlighter-rouge">Add</code>; for the <code class="language-plaintext highlighter-rouge">string</code> we can update it in place; for the <code class="language-plaintext highlighter-rouge">Dictionary</code>, we have to iterate over keys and update each entry. We have to know it’s a <code class="language-plaintext highlighter-rouge">List</code> beforehand to know to use <code class="language-plaintext highlighter-rouge">foreach</code>. We have to know it’s just a <code class="language-plaintext highlighter-rouge">string</code> to append another string to it.</p>

<p>With monads, you delegate the control flow to the monad itself, the monad knows how to update its underlying value(s). Recall that even the simplest monads <strong>must implement two methods to be monads (<code class="language-plaintext highlighter-rouge">Unit</code> and <code class="language-plaintext highlighter-rouge">flatMap</code>) and must follow three monad laws.</strong></p>

<h3 id="unit"><code class="language-plaintext highlighter-rouge">Unit</code></h3>

<p><strong><code class="language-plaintext highlighter-rouge">Unit</code></strong> moves a raw value into the monadic context (this operation is sometimes called “lifting”, “identity”, “return”, “wrap”, or “promotion”, and in some libraries has names like <code class="language-plaintext highlighter-rouge">liftM</code> or <code class="language-plaintext highlighter-rouge">liftA</code>).</p>

<ul>
  <li>
    <p>In the list monad, <strong><code class="language-plaintext highlighter-rouge">Unit</code></strong> takes a single element and returns a list containing that element.</p>
  </li>
  <li>
    <p>For example, given the integer <code class="language-plaintext highlighter-rouge">1</code>, <code class="language-plaintext highlighter-rouge">Unit</code> produces a list as follows:</p>
  </li>
</ul>

<p><strong>Example (C#):</strong></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">list</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="p">{</span> <span class="m">1</span> <span class="p">};</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code> implements <code class="language-plaintext highlighter-rouge">Unit</code> because it allows moving a value into the mondaic context. Nothing about the value <code class="language-plaintext highlighter-rouge">1</code> changes, it’s simply wrapped in a <code class="language-plaintext highlighter-rouge">List</code>. If you access element <code class="language-plaintext highlighter-rouge">0</code> of that list, you get back <code class="language-plaintext highlighter-rouge">1</code>. That’s it.</p>

<hr />

<h3 id="map">Map</h3>

<p><strong><code class="language-plaintext highlighter-rouge">Map</code></strong> applies a function to each value inside the monad.</p>

<p>In <code class="language-plaintext highlighter-rouge">List</code>, <code class="language-plaintext highlighter-rouge">Map</code> runs a function on every element and outputs a new list with that function applied to each element. Don’t overcomplicate it. For example, suppose we have a function that adds one, <code class="language-plaintext highlighter-rouge">f(x) = x + 1</code>. Passing this function to <code class="language-plaintext highlighter-rouge">Map</code> would simply add one to each element in the list. The list <code class="language-plaintext highlighter-rouge">[0,1,2,3]</code> would become <code class="language-plaintext highlighter-rouge">[1,2,3,4]</code>.</p>

<h4 id="example-c-ish"><em>Example (C#-ish):</em></h4>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">originalList</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="p">{</span> <span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">4</span> <span class="p">};</span>    
<span class="kt">var</span> <span class="n">mapped</span> <span class="p">=</span> <span class="n">originalList</span><span class="p">.</span><span class="nf">Map</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span> <span class="p">+</span> <span class="m">1</span><span class="p">);</span> <span class="c1">// `Map` doesn’t exist in C# (use LINQ's `Select`), but assume this pseudocode</span>
</code></pre></div></div>

<p><strong>Example (C#, without monads):</strong></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">originalList</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="p">{</span> <span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">4</span> <span class="p">};</span>    
<span class="kt">var</span> <span class="n">mappedList</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;();</span>

<span class="k">foreach</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="k">in</span> <span class="n">originalList</span><span class="p">)</span>    
<span class="p">{</span>    
    <span class="n">mappedList</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">x</span> <span class="p">+</span> <span class="m">1</span><span class="p">);</span>    
<span class="p">}</span>
</code></pre></div></div>

<h3 id="how-do-you-get-the-damn-values-out-of-the-monads">How do you get the damn values out of the monads?</h3>

<p>Ideally, you don’t want to pull the values out of a monad unless you absolutely have to. It’s possible to implement a <code class="language-plaintext highlighter-rouge">GetValue()</code> method that returns the underlying value, but once the value leaves the monadic context, we lose the benefits of that context and can no longer compose operations easily.</p>

<p>Think about <code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code> as if you had never seen it before. You might say, “I don’t want my values trapped in this list, how am I supposed to use them?” and then manually extract each element into separate variables:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pretend it’s your first time with List&lt;T&gt;</span>
<span class="kt">var</span> <span class="n">numbers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="p">{</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span> <span class="p">};</span>

<span class="c1">// --- Manual extraction (values “trapped” in the list) ---</span>
<span class="kt">var</span> <span class="n">a</span> <span class="p">=</span> <span class="n">numbers</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">b</span> <span class="p">=</span> <span class="n">numbers</span><span class="p">[</span><span class="m">1</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">c</span> <span class="p">=</span> <span class="n">numbers</span><span class="p">[</span><span class="m">2</span><span class="p">];</span>

<span class="c1">// Now call your function separately on each:</span>
<span class="kt">var</span> <span class="n">r1</span> <span class="p">=</span> <span class="nf">AddOne</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">r2</span> <span class="p">=</span> <span class="nf">AddOne</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">r3</span> <span class="p">=</span> <span class="nf">AddOne</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
</code></pre></div></div>

<p>But by doing so, you lose the advantages of using a list in the first place: the ability to store arbitrarily long sequences, to pass around all the values together, to concatenate with other lists, and to iterate easily. If you want to add one to each item, extracting them one by one and handling each separately is tedious and error-prone.</p>

<p>Up to this point, monads might just seem like “fancy containers” that have to implement two odd methods (<code class="language-plaintext highlighter-rouge">Unit</code> and <code class="language-plaintext highlighter-rouge">flatMap</code>). Let’s explore a slightly more complex monad to see why they’re more than just containers.</p>

<h2 id="maybe">Maybe</h2>

<p>Let’s consider a case where unwrapping the value may not always make sense. We’ll create a monad called <code class="language-plaintext highlighter-rouge">Maybe</code> (often also called an <em>Option</em>) which represents either an existing value or the absence of a value.</p>

<p>For simplicity, our <code class="language-plaintext highlighter-rouge">MaybeMonad</code> will hold an <code class="language-plaintext highlighter-rouge">int</code> internally (in a real library this would be a generic <code class="language-plaintext highlighter-rouge">Maybe&lt;T&gt;</code>). It’s not exactly a full monad yet, because we haven’t implemented <code class="language-plaintext highlighter-rouge">flatMap</code> on it.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">MaybeMonad</span> <span class="p">{</span>    
    <span class="k">private</span> <span class="kt">int</span> <span class="k">value</span><span class="p">;</span>    
    <span class="k">private</span> <span class="kt">bool</span> <span class="n">hasValue</span><span class="p">;</span>  

    <span class="c1">// Unit    </span>
    <span class="k">public</span> <span class="nf">MaybeMonad</span><span class="p">(</span><span class="kt">int</span> <span class="k">value</span><span class="p">)</span> <span class="p">{</span>    
        <span class="k">this</span><span class="p">.</span><span class="k">value</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="n">hasValue</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="p">}</span>  

    <span class="c1">// Unit (no value)</span>
    <span class="k">public</span> <span class="nf">MaybeMonad</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// hasValue remains false by default</span>
    <span class="p">}</span>  

    <span class="c1">// Map    </span>
    <span class="k">public</span> <span class="n">MaybeMonad</span> <span class="nf">Map</span><span class="p">(</span><span class="n">Func</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="p">&gt;</span> <span class="n">func</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">hasValue</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nf">MaybeMonad</span><span class="p">(</span><span class="nf">func</span><span class="p">(</span><span class="k">value</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here, the <em><code class="language-plaintext highlighter-rouge">Unit</code></em> operation corresponds to calling one of the constructors, that’s how we lift a raw value into a <code class="language-plaintext highlighter-rouge">MaybeMonad</code>. The <em><code class="language-plaintext highlighter-rouge">Map</code></em> operation might feel a bit strange because we’re just dealing with a single value (or none), whereas you might be used to mapping over a list of many values.</p>

<p>For example, to add 1 to a <code class="language-plaintext highlighter-rouge">MaybeMonad</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">age</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MaybeMonad</span><span class="p">(</span><span class="m">30</span><span class="p">);</span>    
<span class="kt">var</span> <span class="n">newAge</span> <span class="p">=</span> <span class="n">age</span><span class="p">.</span><span class="nf">Map</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span> <span class="p">+</span> <span class="m">1</span><span class="p">);</span>    
<span class="c1">// newAge now holds 31</span>
</code></pre></div></div>

<p>Or if there was no value to begin with:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">age</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MaybeMonad</span><span class="p">();</span>    
<span class="kt">var</span> <span class="n">newAge</span> <span class="p">=</span> <span class="n">age</span><span class="p">.</span><span class="nf">Map</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span> <span class="p">+</span> <span class="m">1</span><span class="p">);</span>    
<span class="c1">// newAge is still “nothing”, `Map` didn’t call `f(x)` because there was no value</span>
</code></pre></div></div>

<p>This looks verbose just to add 1 to a number. Why wrap <code class="language-plaintext highlighter-rouge">30</code> in a <code class="language-plaintext highlighter-rouge">MaybeMonad</code> and call <code class="language-plaintext highlighter-rouge">Map</code> when we could have just incremented <code class="language-plaintext highlighter-rouge">30</code> directly? The point is that <code class="language-plaintext highlighter-rouge">age</code> is a <code class="language-plaintext highlighter-rouge">MaybeMonad</code>, by definition it might or might not contain a value. In the case where there is no value, <code class="language-plaintext highlighter-rouge">MaybeMonad</code>’s <code class="language-plaintext highlighter-rouge">Map</code> simply does nothing. You’d have to write the same conditional logic yourself in a procedural style:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span><span class="p">?</span> <span class="n">age</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>    
<span class="k">if</span> <span class="p">(</span><span class="n">age</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="n">age</span><span class="p">++;</span>
</code></pre></div></div>

<p>Or:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span><span class="p">?</span> <span class="n">age</span> <span class="p">=</span> <span class="m">30</span><span class="p">;</span>    
<span class="k">if</span> <span class="p">(</span><span class="n">age</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="n">age</span><span class="p">++;</span>
</code></pre></div></div>

<p>Now we start to see why a monad is not simply a container to be unwrapped at will. How would you “unwrap” a <code class="language-plaintext highlighter-rouge">MaybeMonad</code>? If it has a value, you could return it, sure. But if it doesn’t, there’s nothing to return, the absence itself is a meaningful state. <code class="language-plaintext highlighter-rouge">MaybeMonad</code> essentially encodes the idea of “nothing” (no result) in a way that isn’t just <code class="language-plaintext highlighter-rouge">null</code> (because in many languages <code class="language-plaintext highlighter-rouge">null</code> is still a concrete value of sorts). With <code class="language-plaintext highlighter-rouge">MaybeMonad</code>, if there’s no value, any function passed into <code class="language-plaintext highlighter-rouge">Map</code> simply won’t execute. Unwrapping it and getting a raw value out isn’t always meaningful in this context.</p>

<p>Another benefit of monads is that you can chain computations that themselves produce monadic results. The limitation of only having <code class="language-plaintext highlighter-rouge">Map</code> is that you might end up with nested monads. For example, imagine a function that returns a <code class="language-plaintext highlighter-rouge">Maybe&lt;int&gt;</code>. If you call <code class="language-plaintext highlighter-rouge">Map</code> on a <code class="language-plaintext highlighter-rouge">Maybe&lt;int&gt;</code> with that function, the result would be a <code class="language-plaintext highlighter-rouge">Maybe&lt;Maybe&lt;int&gt;&gt;</code>, a nested container, because the <code class="language-plaintext highlighter-rouge">Map</code> wraps the function’s <code class="language-plaintext highlighter-rouge">Maybe&lt;int&gt;</code> result into yet another <code class="language-plaintext highlighter-rouge">Maybe</code>. We need a way to apply a function that returns a monad and avoid this unnecessary nesting when chaining operations.</p>

<h3 id="flatmap">flatMap</h3>

<p><code class="language-plaintext highlighter-rouge">flatMap</code> is like our <code class="language-plaintext highlighter-rouge">Map</code>, but it also flattens the result. <strong><code class="language-plaintext highlighter-rouge">flatMap</code> provides the ability to chain computations that themselves produce monadic values, which is the defining feature of monads.</strong> For example, if you have a function that looks up a user and returns a <code class="language-plaintext highlighter-rouge">Maybe&lt;User&gt;</code>, but you want to pass it to another function that returns the user’s profile. Using <code class="language-plaintext highlighter-rouge">Map</code> would give you a <code class="language-plaintext highlighter-rouge">Maybe&lt;Maybe&lt;UserProfile&gt;&gt;</code>, an awkward nested container because the input would be a <code class="language-plaintext highlighter-rouge">Maybe&lt;UserProfile&gt;</code>. With <code class="language-plaintext highlighter-rouge">flatMap</code>, you both apply your lookup and collapse the layers in one go, so you can seamlessly sequence optional, error-handling, or asynchronous operations (e.g. promises/tasks) without ever wrestling with nested monadic types.</p>

<p>Here’s what <code class="language-plaintext highlighter-rouge">flatMap</code> looks like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Add this method inside MaybeMonad</span>
<span class="k">public</span> <span class="n">MaybeMonad</span> <span class="nf">FlatMap</span><span class="p">(</span><span class="n">Func</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">MaybeMonad</span><span class="p">&gt;</span> <span class="n">func</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">hasValue</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// Do not wrap again; let the callee decide whether to return a value or "nothing"</span>
        <span class="k">return</span> <span class="nf">func</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">// Propagate "no value"</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Use <code class="language-plaintext highlighter-rouge">flatMap</code> when your next step might also produce “no value,” and you want to keep chaining without ending up with <code class="language-plaintext highlighter-rouge">Maybe&lt;Maybe&lt;int&gt;&gt;</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Maybe</span><span class="p">&lt;</span><span class="n">User</span><span class="p">&gt;</span> <span class="nf">lookupUser</span><span class="p">(</span><span class="kt">string</span> <span class="n">id</span><span class="p">)</span>  
<span class="p">{</span>  
    <span class="c1">// Imagine this calls a database or external service and returns Maybe&lt;User&gt;  </span>
    <span class="k">return</span> <span class="nf">GetUserFromDatabase</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>  
<span class="p">}</span>

<span class="n">Maybe</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">userIdMaybe</span> <span class="p">=</span> <span class="nf">GetUserId</span><span class="p">();</span>

<span class="c1">// Using Map would yield Maybe&lt;Maybe&lt;User&gt;&gt; (nested) because lookupUser returns a Maybe&lt;User&gt;.  </span>
<span class="c1">// This quickly becomes unwieldy and makes further processing difficult.  </span>
<span class="kt">var</span> <span class="n">nested</span> <span class="p">=</span> <span class="n">userIdMaybe</span>  
    <span class="p">.</span><span class="nf">Map</span><span class="p">(</span><span class="n">lookupUser</span><span class="p">);</span>

<span class="c1">// Using flatMap collapses the result to a single Maybe&lt;User&gt;  </span>
<span class="kt">var</span> <span class="n">user</span> <span class="p">=</span> <span class="n">userIdMaybe</span>  
    <span class="p">.</span><span class="nf">FlatMap</span><span class="p">(</span><span class="n">lookupUser</span><span class="p">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">flatMap</code> is arguably more important than <code class="language-plaintext highlighter-rouge">Map</code>, in fact, <code class="language-plaintext highlighter-rouge">flatMap</code> is required to qualify as a monad, and given <code class="language-plaintext highlighter-rouge">flatMap</code> you can implement <code class="language-plaintext highlighter-rouge">Map</code> in terms of it.</p>

<p>What does this chaining look like procedurally? It would be similar to:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">string</span> <span class="n">userId</span> <span class="p">=</span> <span class="nf">GetUserId</span><span class="p">();</span> <span class="c1">// could be null  </span>
<span class="k">if</span> <span class="p">(</span><span class="n">userId</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
  <span class="c1">// e.g., return an error or stop here  </span>
<span class="p">}</span>

<span class="n">User</span> <span class="n">user</span> <span class="p">=</span> <span class="nf">GetUserFromDatabase</span><span class="p">(</span><span class="n">userId</span><span class="p">);</span> <span class="c1">// this could return null (no user found)  </span>
<span class="k">if</span> <span class="p">(</span><span class="n">user</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
  <span class="c1">// handle missing user  </span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>  
  <span class="c1">// we have a valid user  </span>
<span class="p">}</span>
</code></pre></div></div>

<p>In the procedural version, we had to explicitly handle the control flow at each step (checking for <code class="language-plaintext highlighter-rouge">null</code> in this case). In the monadic version, the control flow is implicit in the monad. If <code class="language-plaintext highlighter-rouge">userIdMaybe</code> has no value, <code class="language-plaintext highlighter-rouge">flatMap</code> simply doesn’t call <code class="language-plaintext highlighter-rouge">lookupUser</code> at all, the “else do nothing” logic is built into <code class="language-plaintext highlighter-rouge">Maybe</code>.</p>

<p>In the monadic example, you could write:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Maybe</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">userIdMaybe</span> <span class="p">=</span> <span class="nf">GetUserId</span><span class="p">();</span>  
<span class="n">Maybe</span><span class="p">&lt;</span><span class="n">User</span><span class="p">&gt;</span> <span class="n">userMaybe</span> <span class="p">=</span> <span class="n">userIdMaybe</span><span class="p">.</span><span class="nf">FlatMap</span><span class="p">(</span><span class="n">lookupUser</span><span class="p">);</span>
</code></pre></div></div>

<p>The monads handle the control flow for us. <code class="language-plaintext highlighter-rouge">GetUserId()</code> returns a <code class="language-plaintext highlighter-rouge">Maybe</code> because we’re acknowledging the user ID might not exist. We’ve defined the <code class="language-plaintext highlighter-rouge">Maybe</code> monad such that if there’s no value, any subsequent function (like <code class="language-plaintext highlighter-rouge">lookupUser</code>) won’t execute. There’s nothing mystical here, we explicitly designed <code class="language-plaintext highlighter-rouge">Maybe</code> to work that way.</p>

<p>This is why it makes sense to wrap values in monads and keep chaining within the monadic context: you can sequence operations (like getting a user ID, then looking up a user, then perhaps fetching their profile) without writing a single explicit <code class="language-plaintext highlighter-rouge">if</code> or loop for the control flow. Each monad step handles the logic of “if there’s no value, stop here” automatically.</p>

<p>If you prematurely yank a value out of a monad, you end up doing manual work that defeats this benefit. For instance, consider if we had a <code class="language-plaintext highlighter-rouge">GetValue()</code> method to extract the inner value (with <code class="language-plaintext highlighter-rouge">null</code> representing “no value”):</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Maybe</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">userIdMaybe</span> <span class="p">=</span> <span class="nf">GetUserId</span><span class="p">();</span>  
<span class="kt">var</span> <span class="n">actualUserId</span> <span class="p">=</span> <span class="n">userIdMaybe</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">();</span>  
<span class="k">if</span> <span class="p">(</span><span class="n">actualUserId</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
    <span class="c1">// do something with actualUserId  </span>
<span class="p">}</span>
</code></pre></div></div>

<p>Eww. If we treat the monad as just a fancy wrapper to put a value in and then take it out immediately, it does feel like pointless ceremony. This is where many people give up on learning monads, it seems like you’re just putting a value in a box and taking it out again with extra steps. But the power of monads comes when you stay <em>inside</em> the monadic context and keep chaining operations. In Part 2, we’ll look at more advanced monads that aren’t just simple containers, and you’ll see how staying in the monadic pipeline pays off.</p>

<h2 id="closing-the-loop-on-maybe"><strong>Closing the loop on Maybe</strong></h2>

<p>We’re making a few changes to the <code class="language-plaintext highlighter-rouge">Maybe</code> monad to give it a more official, ergonomic API. First, instead of letting callers construct the underlying representation directly, we’ll expose two <em>factory methods</em>: <code class="language-plaintext highlighter-rouge">Some</code> and <code class="language-plaintext highlighter-rouge">None</code>. Second, we’ll generalize map: instead of only mapping over integers, the monad will be generic so it can map any type. Finally, we’ll standardize the name to <code class="language-plaintext highlighter-rouge">Maybe&lt;T&gt;</code>. Together, these tweaks clean things up and make the monad easier to use across more scenarios.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">Maybe</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="kt">bool</span> <span class="n">_has</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">T</span> <span class="n">_value</span><span class="p">;</span>

    <span class="k">private</span> <span class="nf">Maybe</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_has</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
        <span class="n">_value</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nf">Maybe</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">_has</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
        <span class="n">_value</span> <span class="p">=</span> <span class="k">default</span><span class="p">(</span><span class="n">T</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="nf">Some</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">value</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="nf">None</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;</span> <span class="n">Map</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">U</span><span class="p">&gt;</span> <span class="n">f</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_has</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;.</span><span class="nf">Some</span><span class="p">(</span><span class="nf">f</span><span class="p">(</span><span class="n">_value</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;.</span><span class="nf">None</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;</span> <span class="n">Bind</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;&gt;</span> <span class="n">f</span><span class="p">)</span> <span class="c1">// aka FlatMap</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_has</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="nf">f</span><span class="p">(</span><span class="n">_value</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">Maybe</span><span class="p">&lt;</span><span class="n">U</span><span class="p">&gt;.</span><span class="nf">None</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>To wrap up <strong><code class="language-plaintext highlighter-rouge">Maybe</code></strong>: it’s perfect when you only need to model “value or no value.” Often, we also need to know <em>why</em> a value is missing (not found, invalid input, business‑rule violation). <strong><code class="language-plaintext highlighter-rouge">Maybe</code></strong> can’t carry that reason.</p>

<h2 id="monad-laws">Monad Laws</h2>

<p>To be a true monad, a type must not only provide <code class="language-plaintext highlighter-rouge">Unit</code> and <code class="language-plaintext highlighter-rouge">flatMap</code> operations, but also obey three simple laws that make sure these operations behave consistently:</p>

<ol>
  <li><strong>Left Identity:</strong> <code class="language-plaintext highlighter-rouge">Unit(x).flatMap(f)</code> is the same as <code class="language-plaintext highlighter-rouge">f(x)</code>. (Wrapping a value and then immediately applying a function to it is equivalent to just calling the function on the raw value.)</li>
  <li><strong>Right Identity:</strong> <code class="language-plaintext highlighter-rouge">m.flatMap(Unit)</code> is the same as <code class="language-plaintext highlighter-rouge">m</code>. (If you <code class="language-plaintext highlighter-rouge">flatMap</code> a monad with the <code class="language-plaintext highlighter-rouge">Unit</code> function, the monad should remain unchanged.)</li>
  <li><strong>Associativity:</strong> <code class="language-plaintext highlighter-rouge">m.flatMap(f).flatMap(g)</code> is the same as <code class="language-plaintext highlighter-rouge">m.flatMap(x =&gt; f(x).flatMap(g))</code>. (It doesn’t matter how you parenthesize nested <code class="language-plaintext highlighter-rouge">flatMap</code> operations, the outcome will be the same.)</li>
</ol>

<p>You don’t need to memorize these laws, but they provide a mathematical guarantee that monadic operations will compose reliably. Our <code class="language-plaintext highlighter-rouge">MaybeMonad</code> adheres to these laws, making it a true monad.</p>

<p>As we’ve seen, monads provide a context for computation. By defining two core operations, <code class="language-plaintext highlighter-rouge">Unit</code> (to wrap a value) and <code class="language-plaintext highlighter-rouge">flatMap</code> (to sequence operations that produce a new context), we abstract away manual control flow like loops and null-checks. This lets us turn scattered procedural code into a single declarative pipeline.</p>

<p>The real power comes when we apply this pattern to different contexts. In Part 2, we’ll explore other useful monads, like <code class="language-plaintext highlighter-rouge">Either</code> for more descriptive error handling, and see how to combine monads to manage multiple concerns at once.</p>

<p>Exercise for reader: I’d encourage opening up your IDE, without any AI assistance, and implementing the <code class="language-plaintext highlighter-rouge">Maybe</code> monad from scratch (no cheating.)</p>

<p><a href="https://alexyorke.github.io/2025/09/13/monads-in-c-sharp-part-2-result/">Part 2</a></p>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[An approachable introduction to monads via List: Map vs flatMap, sequencing, and the monad laws, aimed at OOP developers.]]></summary></entry><entry><title type="html">Embracing Functional Programming Principles in OOP with Static Factory Methods</title><link href="https://alexyorke.github.io/2024/09/15/embracing-functional-programming-principles-in-oop-with-static-factory-methods/" rel="alternate" type="text/html" title="Embracing Functional Programming Principles in OOP with Static Factory Methods" /><published>2024-09-15T00:00:00+00:00</published><updated>2024-09-15T00:00:00+00:00</updated><id>https://alexyorke.github.io/2024/09/15/embracing-functional-programming-principles-in-oop-with-static-factory-methods</id><content type="html" xml:base="https://alexyorke.github.io/2024/09/15/embracing-functional-programming-principles-in-oop-with-static-factory-methods/"><![CDATA[<p>In functional programming, type safety ensures that values (often called “objects” in other programming paradigms) conform to the constraints defined by their type. This means that functions, data structures, and types can only hold and operate on values that are valid according to the rules of their type. By using constructs like algebraic data types, functional programming prevents the creation of invalid states. A number can only be a number, it can’t be a string for example. So, if I have a function that accepts the Number type, then I know that it will be a number.</p>

<p>When we go into the day-to-day business side of things, things become less clear. For example, a customer’s email is a string–sure, if my function expects the String type, then I’ll get a string. But we have to do defensive validation, cluttering up our code with validation checks everytime we pass this mysterious customer email address string, that might not be valid.</p>

<p>In C#, we can simulate this type safetyness by using the static factory pattern. In this example, the ValidatedObject cannot be instantiated via new(), it must be through the factory. This gives a few advantages, namely, the object (or record in this case) cannot exist unless it is valid. Therefore, since it is also sealed, and immutable (as it is a record type), we don’t have to constantly re-validate it.</p>

<p>Additonally, we get the bonus of being type safe. That is, say a method accepts two strings: a customer name and email. If it’s typed as a CustomerEmail, then it is not possible to compile your code or get customer name and email mixed up in the method params.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static partial class ValidatedObjectCreator
{
    internal sealed record ValidatedObject : IValidatedObject 
    {
        public string Data { get; }

        private ValidatedObject(string data) =&gt; this.Data = data;

        public static IValidatedObject Create(string data)
        {
            if (!IsValid(data)) throw new ArgumentException("Invalid input data.");

            return new ValidatedObject(data);
        }

        // Basic validation logic (can be customized)
        private static bool IsValid(string data) =&gt; data.Length &gt; 3;

        public override string ToString() =&gt; Data;
    }

    internal interface IValidatedObject
    {
        static IValidatedObject Create(string data) =&gt; throw new NotImplementedException();
        // expose properties here as needed
    }
}


public static class Program
{
    public static void Main()
    {
        var validatedObject = ValidatedObjectCreator.ValidatedObject.Create("Test data");
        // this will fail
        // var validatedObject = new ValidatedObject("Test data");
    }
}
</code></pre></div></div>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[How to bring FP-style 'make invalid states unrepresentable' into OOP using static factory methods and validated value objects in C#.]]></summary></entry><entry><title type="html">What -Ops are there?</title><link href="https://alexyorke.github.io/2022/11/16/what-ops-are-there/" rel="alternate" type="text/html" title="What -Ops are there?" /><published>2022-11-16T00:00:00+00:00</published><updated>2022-11-16T00:00:00+00:00</updated><id>https://alexyorke.github.io/2022/11/16/what-ops-are-there</id><content type="html" xml:base="https://alexyorke.github.io/2022/11/16/what-ops-are-there/"><![CDATA[<p>There’s a few common -Ops terms, such as DevOps, GitOps, and ChatOps. Are there more?</p>

<p>I went through about 360GB (gzipped) of journal article keywords from <a href="https://archive.org/details/GeneralIndex">The General Index</a>. One of the datasets in the The General Index is a dataset of keywords extracted from 107 million journal articles.</p>

<p>I made a quick shell script to parse the data and collect all keywords that ended in “Ops” case sensitively. I deleted some that were completely non-sensical (composed of punctuation) and also highlighted some that I thought were particularly interesting.</p>

<p>The following table is some of the -Ops terms.</p>

<table>
  <thead>
    <tr>
      <th><strong>Term</strong></th>
      <th><strong>Google Scholar link</strong></th>
      <th><strong>Interesting?</strong></th>
      <th><strong>Abstract snippet</strong></th>
      <th><strong>Link to paper</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>200Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22200Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>22&amp;25Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%2222&amp;25Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>2OOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%222OOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>3OOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%223OOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>3QOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%223QOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>4OOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%224OOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>5OOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%225OOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>6OOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%226OOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>9OOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%229OOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AbOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AbOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>acceptableOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22acceptableOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ACOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ACOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>activeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22activeOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ActOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ActOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AdOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AdOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AGAOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AGAOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>Agile+DevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22Agile+DevOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://ieeexplore.ieee.org/abstract/document/8239932</td>
    </tr>
    <tr>
      <td>AgOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AgOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AidOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AidOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“a cloud orchestration system that leverages machine learning and domain-specific knowledge to predict the traffic demand, optimizing service performance and cost”</td>
      <td>https://dl.acm.org/doi/10.1145/3127479.3129250</td>
    </tr>
    <tr>
      <td>AIOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AIOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“Artificial Intelligence for IT Operations (AIOps)”</td>
      <td>https://arxiv.org/abs/2012.09108</td>
    </tr>
    <tr>
      <td>AirOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AirOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AlgebraOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AlgebraOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AllMemOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AllMemOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AlphaOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AlphaOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AmpOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AmpOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>anonOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22anonOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AppOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AppOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ArithOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ArithOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ArmyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ArmyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ArrayOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ArrayOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ArrOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ArrOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ArtOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ArtOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AvgOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AvgOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AVOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22AVOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BaseOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BaseOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>basicOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22basicOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BeOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BinaryOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BinaryOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BindLinearOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BindLinearOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BiOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BiOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BitOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BitOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BizDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BizDevOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“BizDevOps as an extension of DevOps, reinforces the collaboration between business, development, and operation stakeholders in the organization in order to enhance the software cycle.”</td>
      <td>https://link.springer.com/chapter/10.1007/978-3-030-58817-5_50</td>
    </tr>
    <tr>
      <td>BizOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BizOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>”"”examines reasons why SOs, especially those undergoing digital transformation (DX), need</td>
      <td> </td>
    </tr>
    <tr>
      <td>such an innovation process, called the “BizOps for Digital Transformation in Industry</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>(BDXI)” process”””</td>
      <td>https://www.iiconsortium.org/pdf/BizOps-for-Digital-Transformation-in-Industries-Whitepaper.pdf</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BlockingOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BlockingOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BnOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BnOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BookOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BookOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BucketOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BucketOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BufferOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BufferOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BusinessOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BusinessOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>BusOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22BusOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>C!Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22C!Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>C.R.Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22C.R.Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CadOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CadOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CallCenterOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CallCenterOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CatOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CatOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CCDOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CCDOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CConOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CConOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CCOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CCOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CeilOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CeilOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CFOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CFOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ChannelOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ChannelOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ChatOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ChatOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://ieeexplore.ieee.org/document/8527810</td>
    </tr>
    <tr>
      <td>CheckpointOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CheckpointOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ChemOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ChemOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>Chpt11Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22Chpt11Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ChronoOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ChronoOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CitiOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CitiOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CivOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CivOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CloudOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CloudOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“In this paper, we propose a novel CloudOps workflow (extending the traditional DevOps pipeline), proposing techniques and methods for applications’ operators to fully embrace the possibilities of the Cloud Continuum.”</td>
      <td>https://www.mdpi.com/2076-3417/12/9/4347</td>
    </tr>
    <tr>
      <td>CMSOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CMSOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CommonOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CommonOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ComOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ComOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>compareOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22compareOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ComponentOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ComponentOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CompositeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CompositeOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ConchisiOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ConchisiOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CongestionOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CongestionOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ConOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ConOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ContOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ContOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>COps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22COps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>countOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22countOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>createOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22createOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CrOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CrOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CSOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CSOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>cValidOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22cValidOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CvOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CvOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CyberOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CyberOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“Cybersecurity operations (CyberOps) is the use and application of cybersecurity capabilities to a domain, department, organisation or nation. It is fundamentally to protect digital investments, contribute to national economic wellbeing by providing a safe, secure and conducive environment to conduct business and to protect a nation’s critical national infrastructures and citizens welfare. “</td>
      <td>https://c-mric.com/100134</td>
    </tr>
    <tr>
      <td>CycleOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CycleOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CyclOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CyclOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>CyNetOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22CyNetOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“cyber network operations (CyNetOps)”</td>
      <td>https://apps.dtic.mil/sti/pdfs/ADA516590.pdf</td>
    </tr>
    <tr>
      <td>DaC_Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DaC_Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DataOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DataOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“DataOps, a recently coined term by data scientists, data analysts and data engineers refer to a general process aimed to shorten the end-to-end data analytic life-cycle time by introducing automation in the data collection, validation, and verification process.”</td>
      <td>https://dl.acm.org/doi/10.1145/3379177.3388909</td>
    </tr>
    <tr>
      <td>DbgOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DbgOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DBOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DBOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“Database Ops (DbOps)”</td>
      <td>https://repozitorij.unizg.hr/islandora/object/srce:531</td>
    </tr>
    <tr>
      <td>DDOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DDOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DeckOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DeckOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DefOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DefOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>degenOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22degenOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DepOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DepOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DesOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DesOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DevDocOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DevDocOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“This paper proposes, implements, and evaluates an integrated approach, DevDocOps, for continuous automated documentation, in particular for DevOps.”</td>
      <td>https://onlinelibrary.wiley.com/doi/10.1002/spe.2770</td>
    </tr>
    <tr>
      <td>DevinOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DevinOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td>https://gupea.ub.gu.se/handle/2077/62542</td>
    </tr>
    <tr>
      <td>DevMLOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DevMLOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DevOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“DevOps is the combination of Development and Operations and a new way of thinking in the software engineering domain.”</td>
      <td>https://www.inderscienceonline.com/doi/abs/10.1504/IJASM.2020.112343</td>
    </tr>
    <tr>
      <td>DevSecOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DevSecOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“DevSecOps is an emerging paradigm that breaks the Security Team Silo into the DevOps Methodology and adds security practices to the Software Development Cycle (SDL).”</td>
      <td>https://link.springer.com/chapter/10.1007/978-3-030-29608-7_7</td>
    </tr>
    <tr>
      <td>DigTwinOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DigTwinOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“In this paper, we propose DigTwinOps, a Digital Twin framework for Runtime Verification of Cyber-Physical Production Systems (CPPSs).”</td>
      <td>https://www.hindawi.com/journals/je/2019/2875236/</td>
    </tr>
    <tr>
      <td>DiscOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DiscOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DisplayOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DisplayOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DjOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DjOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DROps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DROps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DSOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DSOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DSPL4DevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DSPL4DevOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>DynamicOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22DynamicOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ECOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ECOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>EDOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22EDOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>EIOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22EIOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ElementOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ElementOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ElOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ElOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>emOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22emOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>EntOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22EntOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>EOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22EOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ERMOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ERMOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ExaOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ExaOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ExecuteOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ExecuteOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ExpOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ExpOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ExtOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ExtOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>extraOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22extraOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FacOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FacOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FactOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FactOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FastOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FastOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FedBizOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FedBizOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FieldOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FieldOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FinOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FinOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“Organisations need to take a structured and proactive approach to predicting, tracking and managing the cost of their cloud services to meet their financial objectives”</td>
      <td>https://academic.oup.com/itnow/article-abstract/64/3/54/6672552</td>
    </tr>
    <tr>
      <td>FirstOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FirstOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FLEMOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FLEMOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FlexOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FlexOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FlightOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FlightOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“The system, called FlightOps, handles all aspects of fractional fleet management: reservations, scheduling, dispatch, aircraft maintenance, and crew requirements.”</td>
      <td>https://pubsonline.informs.org/doi/10.1287/inte.33.5.22.19243</td>
    </tr>
    <tr>
      <td>FlinkOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FlinkOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FloatOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FloatOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FLOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FLOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FlowOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FlowOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ForeignOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ForeignOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ForOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ForOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FTOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FTOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>FullOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22FullOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GenericOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GenericOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GeneticOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GeneticOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>geNOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22geNOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GeOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GigaOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GigaOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GitOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GitOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“GitOps lowers the bar for creating self-service versions of common IT processes, making it easier to meet the return in the ROI calculation. GitOps not only achieves this, but also encourages desired behaviors in IT systems: better testing, reduction of bus factor, reduced wait time, more infrastructure logic being handled programmatically with IaC, and directing time away from manual toil toward creating and maintaining automation.”</td>
      <td>https://dl.acm.org/doi/10.1145/3236386.3237207</td>
    </tr>
    <tr>
      <td>GOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GovOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GovOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“In this paper, we introduce GovOps – a novel approach and a conceptual model for cloud-based, dynamic governance of software-defined IoT cloud systems. By introducing a suitable GovOps reference model and a dedicated GovOps manager, it simplifies realizing governance processes and enables performing custom governance tasks more efficiently in practice.”</td>
      <td>https://link.springer.com/chapter/10.1007/978-3-319-22885-3_3</td>
    </tr>
    <tr>
      <td>GPOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GPOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GraphicOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GraphicOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GraphOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GraphOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GroundOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GroundOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>guaranteedOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22guaranteedOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GunOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22GunOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HasTextOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HasTextOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HazOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HazOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HeavyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HeavyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HF.lOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HF.lOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>hilllOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22hilllOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HostOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HostOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HumanOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HumanOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://centaur.reading.ac.uk/92769/</td>
    </tr>
    <tr>
      <td>HuOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HuOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>HyperOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22HyperOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>I-2Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22I-2Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>I’Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22I’Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>iDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22iDevOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“This tutorial presentation describes the state-of-the-art in the emerging area of continuous testing in a DevOps context. It specifies the building blocks of a strategy for continuous testing in industrial-grade DevOps projects (iDevOps) and shares our motivations, achievements, and experiences on our journey to transform testing into the iDevOps world.”</td>
      <td>https://dl.acm.org/doi/10.1145/3183440.3183465</td>
    </tr>
    <tr>
      <td>IdOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IdOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>IHOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IHOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>illegalOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22illegalOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ImageOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ImageOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ImplementOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ImplementOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ImprovedOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ImprovedOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>IndOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IndOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>InfoOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22InfoOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://wuwr.pl/sfzh/article/view/1326</td>
    </tr>
    <tr>
      <td>InfOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22InfOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>InsOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22InsOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>IntegerOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IntegerOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>interestOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22interestOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>IntervalOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IntervalOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>IntMathOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IntMathOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>InventoryOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22InventoryOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>IOOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IOOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>IOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ITOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ITOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3835090</td>
    </tr>
    <tr>
      <td>IViewOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22IViewOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>J’Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22J’Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>JOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22JOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>KeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22KeOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>KIOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22KIOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>KnowOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22KnowOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://www.usenix.org/legacy/event/hotice11/tech/full_papers/Chen.pdf</td>
    </tr>
    <tr>
      <td>KomOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22KomOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>KOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22KOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>L8Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22L8Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LargeDiscOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LargeDiscOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LegalOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LegalOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“We present a new, large-scale corpus for training and evaluating text summarization systems on legal opinions, called LegalOps.”</td>
      <td>https://ieeexplore.ieee.org/document/9378308</td>
    </tr>
    <tr>
      <td>LexOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LexOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LibOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LibOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LieAlgOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LieAlgOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LightOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LightOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LinOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LinOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>lIOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22lIOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LispOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LispOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LiveOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LiveOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LoadOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LoadOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LocalOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LocalOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LogicOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LogicOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LogOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LogOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>lOOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22lOOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>lOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22lOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>LRnOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22LRnOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>lSOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22lSOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MacOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MacOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MaintOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MaintOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ManagedOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ManagedOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MAROps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MAROps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>mathOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22mathOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MATOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MATOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MatrixOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MatrixOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MbDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MbDevOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MedOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MedOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MemOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MemOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MergeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MergeOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MessageOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MessageOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>Meta_Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22Meta_Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MetOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MetOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MfgOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MfgOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MicroOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MicroOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MilOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MilOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MLOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MLOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“Following continuous software engineering practices, there has been an increasing interest in rapid deployment of machine learning (ML) features, called MLOps.”</td>
      <td>https://ieeexplore.ieee.org/document/9474355</td>
    </tr>
    <tr>
      <td>MMOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MMOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MnOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MnOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ModifyUserOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ModifyUserOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MonadOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MonadOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MoneyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MoneyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MrOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MrOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MuOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MuOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>mvOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22mvOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>MyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22MyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NAESBOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NAESBOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NavOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NavOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NbOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NbOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NEOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NEOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NEROps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NEROps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NetArmOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NetArmOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NetOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NetOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“Network Operations (NetOps)”</td>
      <td>https://ieeexplore.ieee.org/document/4086463</td>
    </tr>
    <tr>
      <td>NetSecOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NetSecOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://arxiv.org/pdf/1710.03129.pdf#page=42</td>
    </tr>
    <tr>
      <td>NeuralOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NeuralOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>nextOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22nextOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NGOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NGOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NodeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NodeOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NoOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NoOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td>“However, nowadays serverless computing offers a new way of developing and deploying cloud-native applications. Serverless computing also called NoOps, offloads management and server configuration (operations work) from the user to the cloud provider and lets the user focus only on the product developments.”</td>
      <td>https://link.springer.com/chapter/10.1007/978-3-030-72369-9_8</td>
    </tr>
    <tr>
      <td>nOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22nOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NrOfOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NrOfOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NumberOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NumberOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>NumOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22NumOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>O.OTOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22O.OTOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ofDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ofDevOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>OffOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22OffOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>OneOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22OneOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>OOOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22OOOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>OOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22OOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>OutOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22OutOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>pdateOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22pdateOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PDB_Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PDB_Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PendingOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PendingOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>perOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22perOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PetaOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PetaOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PetrolOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PetrolOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PGOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PGOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PharmOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PharmOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>pHyperOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22pHyperOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PILOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PILOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>pipelineOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22pipelineOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PlanOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PlanOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>pLMCOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22pLMCOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PolyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PolyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>POps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22POps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PortOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PortOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PowerOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PowerOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PreOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PreOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PrisonOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PrisonOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ProdOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ProdOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ProOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ProOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PropOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PropOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ProxOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ProxOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PsyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PsyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>PyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22PyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>qOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22qOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>QuietOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22QuietOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>QuOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22QuOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>R3Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22R3Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RandomOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RandomOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RapidOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RapidOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RasterOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RasterOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>readyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22readyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RefactorOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RefactorOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://www.ideals.illinois.edu/items/11214</td>
    </tr>
    <tr>
      <td>RegOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RegOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://link.springer.com/chapter/10.1007/978-3-030-91452-3_20</td>
    </tr>
    <tr>
      <td>RelOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RelOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ReOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ReOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ResDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ResDevOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://ieeexplore.ieee.org/abstract/document/7765530</td>
    </tr>
    <tr>
      <td>ResOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ResOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RestOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RestOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RhetOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RhetOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RiskOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RiskOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://www.emerald.com/insight/content/doi/10.1108/eb043503/full/html</td>
    </tr>
    <tr>
      <td>RMDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RMDevOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://dl.acm.org/doi/abs/10.1145/3383219.3383278</td>
    </tr>
    <tr>
      <td>RoboOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RoboOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RobOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RobOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RootOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RootOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>rOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22rOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>RSOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22RSOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>rtGovOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22rtGovOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SafeOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SafeOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://link.springer.com/chapter/10.1007/978-3-031-14862-0_11</td>
    </tr>
    <tr>
      <td>Sardil1Ops</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22Sardil1Ops%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SatOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SatOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ScalOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ScalOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ScanOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ScanOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SchedOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SchedOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ScienceOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ScienceOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SciOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SciOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SecDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SecDevOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://ieeexplore.ieee.org/abstract/document/7784617</td>
    </tr>
    <tr>
      <td>SecOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SecOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SecureOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SecureOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SEDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SEDevOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SeekOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SeekOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>selectOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22selectOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SemOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SemOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>sensitiveOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22sensitiveOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ServerOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ServerOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://ieeexplore.ieee.org/abstract/document/6068426</td>
    </tr>
    <tr>
      <td>ShadowOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ShadowOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ShOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ShOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SimOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SimOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SiteOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SiteOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>slOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22slOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SmartOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SmartOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>smOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22smOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SMOSOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SMOSOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SMVOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SMVOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SofOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SofOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SolarOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SolarOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SoOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SoOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>sortedOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22sortedOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SortOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SortOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>spacedOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22spacedOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SpaceOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SpaceOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SparkOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SparkOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SpatialOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SpatialOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SpecialOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SpecialOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SpecificOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SpecificOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SpecOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SpecOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SpirOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SpirOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SpyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SpyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SQLOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SQLOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>StabOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22StabOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>StackOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22StackOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>StdOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22StdOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>stOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22stOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>StoreOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22StoreOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>StringOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22StringOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>StuffOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22StuffOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>superOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22superOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SusOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SusOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SwarmOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SwarmOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SYNOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SYNOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SyOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://www.taylorfrancis.com/books/mono/10.4324/9780429281785/information-security-employee-behaviour-angus-mcilwraith</td>
    </tr>
    <tr>
      <td>SysOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SysOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>SystemWideOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22SystemWideOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TacOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TacOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TaxOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TaxOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://www.scholars.northwestern.edu/en/publications/taxops-giving-expert-advice-to-experts</td>
    </tr>
    <tr>
      <td>TechjOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TechjOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TechOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TechOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TelOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TelOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TeraOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TeraOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TestOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TestOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td>https://link.springer.com/chapter/10.1007/978-3-030-58858-8_26</td>
    </tr>
    <tr>
      <td>TexOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TexOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TiogaOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TiogaOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TopologyOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TopologyOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>tOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22tOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TpdOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TpdOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TrainOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TrainOps%22&amp;btnG=</td>
      <td>TRUE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TransportOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TransportOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TrgOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TrgOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TriOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TriOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TwitterBotDevOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TwitterBotDevOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TwoOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TwoOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>TxOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22TxOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>UndoOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22UndoOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>UnitOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22UnitOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>UnixOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22UnixOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>UnOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22UnOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>uOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22uOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>UserOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22UserOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>validOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22validOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>VarOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22VarOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>VecOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22VecOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>VectorOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22VectorOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>VetOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22VetOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>VictorOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22VictorOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ViewerOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ViewerOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>VisiOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22VisiOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>VisualOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22VisualOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>vROps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22vROps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>WaveOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22WaveOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>WebOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22WebOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>WindOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22WindOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>XmtOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22XmtOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>XOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22XOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ZecOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ZecOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ZOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ZOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>µOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22µOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>ΣOps</td>
      <td>https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C5&amp;q=%22ΣOps%22&amp;btnG=</td>
      <td>FALSE</td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[A data-driven survey of '-Ops' terms mined from 360GB of journal article keywords, with a curated table and links.]]></summary></entry><entry><title type="html">GZIP exceptions, but only on hot or rainy days</title><link href="https://alexyorke.github.io/2022/11/11/gzip-exceptions-but-only-on-hot-or-rainy-days/" rel="alternate" type="text/html" title="GZIP exceptions, but only on hot or rainy days" /><published>2022-11-11T00:00:00+00:00</published><updated>2022-11-11T00:00:00+00:00</updated><id>https://alexyorke.github.io/2022/11/11/gzip-exceptions-but-only-on-hot-or-rainy-days</id><content type="html" xml:base="https://alexyorke.github.io/2022/11/11/gzip-exceptions-but-only-on-hot-or-rainy-days/"><![CDATA[<p>Want to get notified when part two (extended version) releases? Sign up to the mailing list here: http://eepurl.com/idzoGv</p>

<p>It was a hot summer day inside my apartment. At least not for long. I just got an air conditioner from my landlord and it was time to fire it up.</p>

<p><em>A few weeks later</em></p>

<p>I was in the middle of writing a program to decompress some gzip files in C# and I got a strange exception that indicated that the archive was corrupted:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Unhandled exception. System.IO.InvalidDataException: The archive entry was compressed using an unsupported compression method.

System.IO.Compression.Inflater.Inflate(FlushCode flushCode)
     at System.IO.Compression.Inflater.ReadInflateOutput(Byte* bufPtr, Int32 length, FlushCode flushCode, Int32&amp; bytesRead)
     at System.IO.Compression.Inflater.ReadOutput(Byte* bufPtr, Int32 length, Int32&amp; bytesRead)
     at System.IO.Compression.Inflater.InflateVerified(Byte* bufPtr, Int32 length)
     at System.IO.Compression.DeflateStream.ReadCore(Span`1 buffer)
     at System.IO.Compression.DeflateStream.Read(Byte[] array, Int32 offset, Int32 count)
     at System.IO.StreamReader.ReadBuffer()
     at System.IO.StreamReader.ReadLine()
     at MyApp.Program.ReadAllZippedLines(String filename)+MoveNext()
     at System.Linq.Enumerable.EnumerablePartition`1.MoveNext()
     at MyApp.Program.Main(String[] args)
     at MyApp.Program.&lt;Main&gt;(String[] args)
</code></pre></div></div>

<p>This was very strange, because the files were not normally corrupted. This was very concerning because I would have lost a lot of data if that were the case. Out of irrationality, I re-ran the program, and it worked? Weird, I thought. Maybe Windows had the file open with an anti-virus scanner for a second and I caught it at a bad time.</p>

<p>But nevertheless, the error returned again a few minutes later. I decided to turn off Windows Defender, rebooted my laptop, and tried again. No luck. The strange thing was the error was not predictable, sometimes it would work, other times not.</p>

<p>When I re-ran it again, Visual Studio waited on the line (due to an exception handler.) I was able to inspect the code at that point (located in <code class="language-plaintext highlighter-rouge">C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.16\System.IO.Compression.dll</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>internal int ReadCore(Span &lt;byte&gt; buffer) {

  this.EnsureDecompressionMode();

  this.EnsureNotDisposed();

  this.EnsureBufferInitialized();

  int start = 0;

  while (true) {

    do {

      int num = this._inflater.Inflate(buffer.Slice(start));

      start += num;

      if (start == buffer.Length || this._inflater.Finished() &amp;&amp; (!this._inflater.IsGzipStream() || !this._inflater.NeedsInput()))

        goto label_7;

    }

    while (!this._inflater.NeedsInput());

    int count = this._stream.Read(this._buffer, 0, this._buffer.Length);

    if (count &gt; 0) {

      if (count &lt;= this._buffer.Length) &lt; this conditional was false

      this._inflater.SetInput(this._buffer, 0, count);

      else

        break;

    } else

      goto label_7;

  }

  throw new InvalidDataException(SR.GenericInvalidData);

  label_7:

    return start;

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

<p>This was strange, why was the buffer length greater than the count, but only sometimes? This would indicate that the buffer’s length or the count variable were being updated to an incorrect value. The file is the same and hasn’t changed (according to its SHA1 hash.) Except, I couldn’t get the hash the second time due to a strange internal exception error in the plugin for File Explorer I was using.</p>

<p>Could I have discovered a race condition? It didn’t look like the gzip decompressor was multi-threaded. I continued to dig deeper down the callstack,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private ZLibNative.ErrorCode Inflate(ZLibNative.FlushCode flushCode) {

  ZLibNative.ErrorCode errorCode;

  try {

    errorCode = this._zlibStream.Inflate(flushCode);

  } catch (Exception ex) {

    throw new ZLibException(SR.ZLibErrorDLLLoadError, ex);

  }

  switch (errorCode) {

  case ZLibNative.ErrorCode.BufError:

    return errorCode;

  case ZLibNative.ErrorCode.MemError:

    throw new ZLibException(SR.ZLibErrorNotEnoughMemory, "inflate_", (int) errorCode, this._zlibStream.GetErrorMessage());

  case ZLibNative.ErrorCode.DataError:

    throw new InvalidDataException(SR.UnsupportedCompression);

  case ZLibNative.ErrorCode.StreamError:

    throw new ZLibException(SR.ZLibErrorInconsistentStream, "inflate_", (int) errorCode, this._zlibStream.GetErrorMessage());

  case ZLibNative.ErrorCode.Ok:

  case ZLibNative.ErrorCode.StreamEnd:

    return errorCode;

  default:

    throw new ZLibException(SR.ZLibErrorUnexpected, "inflate_", (int) errorCode, this._zlibStream.GetErrorMessage());

  }

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

<p>It was returning a DataError or a StreamError, sometimes. This meant that there was something wrong with the data or the stream (yes, a bit obvious.)</p>

<p>To rule out if it was a coding error, I tried to decompress the gzip file with bash via gzip -dc file. bash threw a strange error, <a href="https://stackoverflow.com/questions/3838322/bash-read-write-file-descriptors-seek-to-start-of-file">“can’t seek file descriptor”</a> when trying to read the file. This error is emitted from <a href="https://github.com/bminor/bash/blob/f3a35a2d601a55f337f8ca02a541f8c033682247/input.c">bash.c here</a>. I also tried decompressing several other gzip files, but they were unable to be decompressed.</p>

<p>At this point, I strongly suspected data corruption. I quickly checked CrystalDiskInfo for any reallocated sectors but could not find any. I then ran sfc.exe /SCANNOW, and I believe it did find errors and corrected them. I ran it again, and again, and again, and it kept fixing errors. At some point (maybe five times) it didn’t report any more errors.</p>

<p>Over the next few weeks, I began to have very strange issues with my laptop. Apps would take forever to load (and sometimes not at all), icons would be missing, and text would appear garbled. I figured my laptop was failing at this point. But one mystery remained: there was no incidents of strange behavior when on battery power. Only when plugged in. And why was my monitor, that I recently purchased, also having issues?</p>
<h3 id="time-to-go-deeper">Time to go deeper</h3>
<p>What caught my attention was that the wrist-rest was very uncomfortable. It was an aging 2012 MacBook Pro, running Boot Camp, but there was no signs of wear on the aluminum. Again, the same pattern came up: the wrist-rest was only uncomfortable when running on AC power.</p>

<p><em>Aside: at this point, I had to repair Visual Studio about two or three times due to strange ungoogle-able errors.</em></p>

<p>It all clicked when I got a very bad shock when I touched my laptop’s frame: could it be an electrical issue?</p>

<h3 id="electricity-going-where-it-shouldnt">Electricity going where it shouldn’t</h3>
<p>Let’s recap on what I found out so far:</p>

<p>- There was a strange green and red noise on my brand new monitor.</p>

<p>- There was a strange green and red noise on my old monitor.</p>

<p>- The trackpad was spicy.</p>

<p>- There (appeared to be) data corruption.</p>

<p>- I got shocked by my laptop.</p>

<p>- Sometimes my apartment lights turned on a little bit by themselves too after I turned them off.</p>

<p>It seems strange that, just out of coincidence, my old and new monitor both exhibited the exact same issue. I also upgraded my laptop’s battery earlier in the year, so perhaps I could have made a mistake?</p>

<p>What could cause things that shouldn’t conduct electricity to conduct it? There could be many issues:</p>

<p>- A bad device that’s wired incorrectly.</p>

<p>- Grounding issues.</p>

<p>- Potentially more issues.</p>

<p>To rule out if it’s caused by another device, as everything was connected using a power strip, I unplugged everything and plugged my laptop directly into a GFCI outlet in the bathroom and turned on all devices and ran prime95 for about 15 minutes. It seemed to be ok. I then plugged everything else in and didn’t have any issues.</p>

<p>And then the issues came back. But only sometimes, or so I thought. The issues were correlated with if it was raining outside or if it was humid. Could it be too much humidity causing a short-circuit?</p>

<p>Apple says <a href="https://support.apple.com/en-us/HT201640#:~:text=You%20should%20also%20use%20your,and%2095%25%20\(noncondensing\).">“You should also use your Mac notebook where the relative humidity is between 0% and 95% (noncondensing)”</a> and the ambient temperature was in-spec. Just to make sure, I turned on my air conditioning at full blast to lower the temperature and humidity. But that just made things worse.</p>

<p>Could it be that the air was too dry? I tried to increase the air conditioner’s temperature but that didn’t help.</p>

<p>But what I did notice was that the issues correlated with having the air conditioner on, which was correlated with humid conditions (because it was hot, so I turned on the AC.)</p>

<p>At this point I suspected an electrical issue. I contacted my landlord to send out an electrician. A service personnel came out and suggested I get an AVR UPS (auto-voltage regulator.)</p>

<p>These are fancy UPSes that automatically adjust the voltage to a desired level if it dips below a threshold (in my case, 120V.) This was because I noticed that the lights browned out sometimes, and he suspected my technical issues might be because of the voltage dips causing issues.</p>

<p>So, I got one. And I was pretty happy, because it was beeping a lot (which meant it was detecting issues and fixing them.)</p>

<p>It was beeping too much, actually. Beeping during calls, beeping during lunch, beeping during trying to sleep. It just kind of snuck up on me, drinking a glass of water and then…<em>beeeep!</em> Thankfully, I was just drinking water.</p>

<p>I decided to silence the beeps as they were getting distracting and waking me up in the night. I checked the stats and it fixed at least 20 power incidents in only a few days. Wow, it’s working pretty hard I thought.</p>

<p>Unfortunately, after a few more days, the issues reappeared.</p>
<h3 id="grounding">Grounding</h3>
<p>The next order of business was to check if there was a grounding issue, according to my research. The “ground” is a path to earth to direct excess or unwanted electrical energy safely away. If there is an issue with the ground, then the unwanted electricity might not be dissipating (I probably butchered this term, I’m not an electrician, but if you have any suggestions on how to improve this let me know.)</p>

<p>A quick check with a $10.00 outlet tester confirmed that there was a grounding issue. The tester confirmed that there was no ground.</p>

<p><em>Aside: At this point, my laptop was bordering on unusable, even on battery power. Random shutdowns and the screen would turn black. Many events appeared in the Event Log, related to APIC power issues. I bought a new laptop in the meantime.</em></p>

<p>Armed with my new knowledge, I asked my landlord to send in an electrician this time. They asked lots of questions, and then recommended that my air conditioner stay on a different outlet because there were two circuits. I tried this for a few days, but the issues still reappeared.</p>

<p>I asked them to come again, and this time they installed a completely new outlet and the outlet was <em>correctly</em> grounded using my water heater as ground, according to my outlet tester.</p>

<p>At last, although one laptop was down, there were no more issues. No issues with strange colors on my monitor, no spicy trackpad, and no shocks.</p>

<p>Sometimes, debugging enters the real world.</p>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[A debugging story about a mysterious C# gzip decompression exception and what actually caused it, with investigation steps and lessons learned.]]></summary></entry><entry><title type="html">How to change GitHub cache action compression level</title><link href="https://alexyorke.github.io/2021/09/20/how-to-change-github-cache-action-compression-settings/" rel="alternate" type="text/html" title="How to change GitHub cache action compression level" /><published>2021-09-20T00:00:00+00:00</published><updated>2021-09-20T00:00:00+00:00</updated><id>https://alexyorke.github.io/2021/09/20/how-to-change-github-cache-action-compression-settings</id><content type="html" xml:base="https://alexyorke.github.io/2021/09/20/how-to-change-github-cache-action-compression-settings/"><![CDATA[<p>The <a href="https://github.com/actions/cache">GitHub cache action</a> is an action that allows you to cache files in between CI runs. However, there isn't a publicly documented way to modify the compression settings (i.e. to increase or decrease the compression ratio.)</p>

<p>However, changing the <a href="http://zstd/README.md%20at%20cefafc0b6efc1cf31b57c8f7f99a7aa88344644d%20%C2%B7%20facebook/zstd%20(github.com)">ZSTD_CLEVEL</a> environment variable allows you to modify the compression level.</p>

<p>For example, in your GitHub actions YAML file, add the <code class="language-plaintext highlighter-rouge">env</code> stanza:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- name: Cache
uses: actions/cache@v2.1.6
env:
ZSTD_CLEVEL: 19
with:
[...]
</code></pre></div></div>
<p>…To change the compression level.</p>

<h3 id="does-this-actually-work">Does this actually work?</h3>

<p>As of zstd v1.5.0 on September 20th, 2021 on the ubuntu-latest image, it works. If I cache a <a href="http://cachefly.cachefly.net/speedtest/?ref=driverlayer.com/web">100mb test file from Cachefly</a>, GitHub says the cache size is 416162 B. Consequently, after setting the environment variable to 19, GitHub says the cache size is only 38709 B.</p>

<p>The higher-compressed file is 10.75 times smaller than not setting the environment variable, which means that the environment variable did have an effect.</p>

<h3 id="levels-higher-than-19">Levels higher than 19</h3>

<p>This doesn't work with levels higher than 19 because the –ultra flag wasn't specified. If you try to do it anyway, you'll get a message saying "Warning : compression level higher than max, reduced to 19".</p>

<p>It doesn't appear to be possible to manipulate the –ultra flag through the GitHub cache action, but is "possible" if you create your own caching mechanism (i.e. upload the file directly to the cache.)</p>

<p>If you must use GitHub's cache action and still want to compress higher than 19, you can first pre-compress the file using –ultra and level 21, then set the ZSTD_CLEVEL to 1 for the GitHub cache action. This is just a workaround as there is overhead when compressing a file twice, and in some cases could make the file slightly larger.</p>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[How to change GitHub Actions cache compression by setting ZSTD_CLEVEL, including an experiment showing real size differences.]]></summary></entry><entry><title type="html">Quickly estimating unused filesystem blocks</title><link href="https://alexyorke.github.io/2021/09/18/quickly-estimating-unused-filesystem-blocks/" rel="alternate" type="text/html" title="Quickly estimating unused filesystem blocks" /><published>2021-09-18T00:00:00+00:00</published><updated>2021-09-18T00:00:00+00:00</updated><id>https://alexyorke.github.io/2021/09/18/quickly-estimating-unused-filesystem-blocks</id><content type="html" xml:base="https://alexyorke.github.io/2021/09/18/quickly-estimating-unused-filesystem-blocks/"><![CDATA[<p>If you delete a file from a filesystem, it is removed from the index but usually stays on disk until a newer file overwrites it.</p>

<p>It is hard to determine how much data is deleted because file systems usually do not separate zeros from overwritten areas.</p>

<p>Both Testdisk and PhotoRec can restore deleted files (to estimate how much data has been lost but not yet reallocated); however, they can only do this if the file system's index about that file is intact. Also, it operates block by block, so while it is slow as an approximation, it's fantastic for recovering deleted files. Additionally, it takes time to recover other parts of the file, such as the filename, which, again, is valuable for recovering files, but not necessary for an estimate.</p>

<p>One might be concerned with how many files can be undeleted, when sending a disk over a network, and when reassigning those sectors in a server or maintenance environment.</p>

<h3 id="terminology">Terminology</h3>

<p>In this post, a “zeroed block” is a block which just contains zeros on the file system that we're using. In this case, we're mounting an XFS filesystem file, so a “zero block” is one which is pure zeros on that virtual file system; we're not concerned about the physical file system which contains that file.</p>

<p>A free block (defined by XFS) is a block that the file system can use to write data to. It might contain zeros, or it might have deleted file data.</p>

<h3 id="getting-started">Getting started</h3>

<p>Here's an XFS filesystem, approximately 256GB in size, and contains about 80 million files. Let's find out how much data is deleted.</p>

<p>First, let's get the block size.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ xfs_info xfspart.img

meta-data=xfspart.img isize=512 agcount=4, agsize=16777088 blks

= sectsz=4096 attr=2, projid32bit=1

= crc=1 finobt=1, sparse=1, rmapbt=0

= reflink=1

data = bsize=4096 blocks=67108352, imaxpct=25

= sunit=0 swidth=0 blks

naming =version 2 bsize=4096 ascii-ci=0, ftype=1

log =internal log bsize=4096 blocks=32767, version=2

= sectsz=4096 sunit=1 blks, lazy-count=1

realtime =none extsz=4096 blocks=0, rtextents=0
</code></pre></div></div>

<p>The data’s block size is 4096.How many free blocks are there?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ xfs_db -r -f -c 'freesp -s' xfspart.img

from to extents blocks pct

1 1 2546152 2546152 7.49

2 3 911501 2041822 6.01

4 7 135254 623605 1.83

8 15 17445 190110 0.56

16 31 77858 2142232 6.30

32 63 14970 552755 1.63

64 127 1390 123298 0.36

128 255 750 126097 0.37

256 511 231 73905 0.22

512 1023 40 26820 0.08

1024 2047 25 35063 0.10

2048 4095 16 48302 0.14

4096 8191 20 128915 0.38

8192 16383 42 524281 1.54

16384 32767 43 1075196 3.16

32768 65535 46 2342649 6.89

65536 131071 41 3947378 11.61

131072 262143 7 1108540 3.26

262144 524287 5 2231234 6.56

524288 1048575 1 687478 2.02

4194304 8388607 3 13416832 39.47

total free extents 3705840

total free blocks 33992664

average free extent size 9.17273
</code></pre></div></div>

<p>About 50% of the disk is free, which means 50% of the space is reserved. According to the du command, this is correct. This means that “total free blocks” refer to blocks that are available for reallocation, not necessarily zeroed ones.</p>

<p>We could look at the free space extents:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ xfs_db -r -f -c 'freesp -d' xfspart.img | wc -l

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

<p>We would need to convert the agbno's into blocks, and then write a script to sample the intervals uniformly. Is there an alternative way that isn't XFS specific?</p>

<p>Let's start at the block level. We'll print out a few blocks from our file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ xfs_logprint -D -f xfspart.img

[...]

BLKNO: 1505

0 a5355de4 e325a5f5 54cd9df6 6506fc89 ca52b9ff 6ace79d7 ef9eb46f 4c7de7b6

8 426c52d0 439ace42 7dcce422 8042584f ffd90e7e 3c645921 49c1e0e1 92788e42

10 9923bc96 8ec69d91 62e7d648 1a08a9ac a496ba08 b650c6d cbf97496 486d0fee

18 caad8832 8bb79812 773960e9 6fb5aa62 dfcc301b 3ea437f2 28d47707 8caddb2b

20 c3ef6603 e7408c96 d6cf8054 33fded0e fb7ddd8d 4ebb95a0 fb23bf04 93b891c

28 fee01d7b b7e665b5 57fcb8b6 730cd271 c63304d1 abc746be 97384437 c94802f3

30 af38b5d5 888f264f a7a164d2 6665cdbb 49a600f3 368435a4 db25a652 e58cab38

38 1e55f7fb 44708a45 9be66a16 619afffd 6c7c859f 9eb8a9f0 541df29c a3f83150

40 257fe38f 9fce9fa8 be74d00 24b7e1af 7d6666af a8ffc4bb 67e2771a 64810a3e

48 2aa638d1 2e19e242 a0a2d696 3aac42b5 fb05685c bd61ec5c 4fdc662f 856f106c

50 67992c1b 8b2e7579 75adf95 61d1162 2fafd42c b737a2c3 ff23c6aa 39b1f81e

58 b6629743 e0a477bd f07c9065 3776fd0 1ddb6213 61eb5aa3 ff3f736e af1c2fd9

60 d48e6a72 c6ad2e6b cc7ca116 9a5d52d7 c25029c4 7c9f03a4 4bd71895 6bbbfb0e

68 5fc5f9a5 8ff25f73 312d3af3 659cf372 7af223ec 85cca734 1c45febe 99017b68

70 3693035 b3097096 e14e5950 56c6a840 20ca7ab3 db70f6e9 236334c4 ad2a6100

78 fc760869 6ebafb95 a496146 9724741d b00f359a c8ade661 417ef62f 10765443

[...]
</code></pre></div></div>

<p>Let's make sure that this maps to a physical block[0]:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ dd if=xfspart.img bs=512 count=1 skip=1505 | od -t x4 -w32

0000000 a5355de4 e325a5f5 54cd9df6 6506fc89 ca52b9ff 6ace79d7 ef9eb46f 4c7de7b6

0000040 426c52d0 439ace42 7dcce422 8042584f ffd90e7e 3c645921 49c1e0e1 92788e42

0000100 9923bc96 8ec69d91 62e7d648 1a08a9ac a496ba08 0b650c6d cbf97496 486d0fee

0000140 caad8832 8bb79812 773960e9 6fb5aa62 dfcc301b 3ea437f2 28d47707 8caddb2b

0000200 c3ef6603 e7408c96 d6cf8054 33fded0e fb7ddd8d 4ebb95a0 fb23bf04 093b891c

0000240 fee01d7b b7e665b5 57fcb8b6 730cd271 c63304d1 abc746be 97384437 c94802f3

0000300 af38b5d5 888f264f a7a164d2 6665cdbb 49a600f3 368435a4 db25a652 e58cab38

0000340 1e55f7fb 44708a45 9be66a16 619afffd 6c7c859f 9eb8a9f0 541df29c a3f83150

0000400 257fe38f 9fce9fa8 0be74d00 24b7e1af 7d6666af a8ffc4bb 67e2771a 64810a3e

0000440 2aa638d1 2e19e242 a0a2d696 3aac42b5 fb05685c bd61ec5c 4fdc662f 856f106c

0000500 67992c1b 8b2e7579 075adf95 061d1162 2fafd42c b737a2c3 ff23c6aa 39b1f81e

0000540 b6629743 e0a477bd f07c9065 03776fd0 1ddb6213 61eb5aa3 ff3f736e af1c2fd9

0000600 d48e6a72 c6ad2e6b cc7ca116 9a5d52d7 c25029c4 7c9f03a4 4bd71895 6bbbfb0e

0000640 5fc5f9a5 8ff25f73 312d3af3 659cf372 7af223ec 85cca734 1c45febe 99017b68

0000700 03693035 b3097096 e14e5950 56c6a840 20ca7ab3 db70f6e9 236334c4 ad2a6100

0000740 fc760869 6ebafb95 0a496146 9724741d b00f359a c8ade661 417ef62f 10765443
</code></pre></div></div>

<p>Looks like we're on the right track.</p>

<p>We can print out if a block is empty via checking if the hex representation of it is just zeros. We're temporarily using the 512 byte block size (for inodes) so that we can cross-check our work with xfs_db:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ dd if=xfspart.img bs=512 count=1 skip=1505 status=none | hexdump -v -e '/4 "%02x"' | grep -c '^[0]\*$'

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

<p>Let's manually go to a block we know is free (block 15) and see if grep returns 1.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ dd if=xfspart.img bs=512 count=1 skip=15 status=none | hexdump -v -e '/4 "%02x"' | grep -c '^[0]\*$'

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

<p>As we want to total all of the blocks that are zeros, we return one if there is a match, and zero otherwise.</p>

<p>Recall that we have 67108352 blocks in total, so for a conf. interval of 99% and a margin of error of 1%, we have to sample 16637 blocks. The sample size is approximately 68.15MB.</p>

<p>While the amount of data isn't particularly large for an HDD, random accesses slow down the data transfer. Using CrystalDiskMark, we measure the performance of random 4KiB reads. Q1T1 (one queue, one thread to simulate how the data will be accessed) indicates we can read at 0.41MB/s.</p>

<p>As a best case scenario, this will take about a minute and 37 seconds. Since we're running it through WSL2, performance might be impacted further, however.</p>

<p>Let's use 4096 as our data block size, and sample 16637 random blocks:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ for i in $(shuf -i0-67108351 -n16637 | sort -n); do dd if=xfspart.img bs=4096 count=1 skip=$i status=none | hexdump -v -e '/4 "%02x"' | grep -c '^[0]\*$'; done | pv -l -s 16637 | awk '{s+=$1} END {printf "%.0f", s}'

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

<p><em>We don't need PV, but it gives us a progress bar and ETA. To make reading more sequential, I'm sorting the sectors in increasing order.</em></p>

<p><em>It took two minutes and 46 seconds, which was not close to what we expected. As well as running WSL2, I had a lot of apps and backup software running, which could have caused the estimation to be off.</em></p>

<p>So, out of 16637 blocks, 4759 were zeros, which is about 28.60% of zero blocks or 78.61GB.</p>

<p>Since zero blocks are free space[1], then we can subtract them from the reported free space to get the non-zeroed out blocks. We know that 33992664/67108352 blocks or 50.653% is reported as free by XFS from our xfs_info command earlier.</p>

<p>So, reported free space - zero blocks = 50.653% - 28.60% = 22.053% of the disk is non-zeroed blocks.</p>

<p>This means that 22.053% of 67108352 blocks is about <strong>60.62GB of deleted files</strong>. So, our free space is composed of 60.62GB of deleted files and 78.61GB of zeroed blocks, or 139.23GB of free space total.</p>

<p>Is this accurate?</p>

<h3 id="checking-our-work">Checking our work</h3>

<h4 id="cross-checking-with-zeroed-out-filesystem">Cross checking with zeroed out filesystem</h4>

<p>I happened to have a copy of the exact filesystem, except that I've zeroed out the free space, defragged it, then re-zeroed out the free space again. Let's rerun the script on that filesystem.</p>

<p>We get 8493, which means that out of 16637 blocks, 8493 were free, or about 51.04%.</p>

<p>So, reported free space - zero blocks = 50.653% - 51.04% = -0.387% of our disk contains deleted files.</p>

<p>Why is it negative? Well, it's in our 1% margin of error and 99% confidence interval. For all intents and purposes, this is essentially zero.</p>

<p>We're expecting the zeroed out volume to not have any recoverable deleted files so this matches up with our expectations.</p>

<h5 id="what-does-photorec-recover">What does PhotoRec recover?</h5>

<p>After running PhotoRec on the partition, it recovered 45GB of data (“du” apparent size.) This is the same order of magnitude of what we estimated, however, it is less because we are working at a file system block level rather than a file level. This means that even if a file occupied only one byte of data, we would have counted it as 512 bytes.</p>

<h4 id="compressing-both-disks">Compressing both disks</h4>

<p>Compressing the zeroed out disk using zstd with default options gives us a ratio of 16.84 (15.2GB), while the non-zeroed out disk is 6.26 (40.9GB); each disk was 256GB on-disk.</p>

<p>The difference between these two compressed files is 25.7GB. We have 60.62GB of deleted files, so let's go to the first disk.</p>

<p>The non-zeroed out disk had 128GB of data, plus 60.62GB of deleted data which gives 188.62GB of data to compress. Consequently, the zeroed out disk only had 128GB of data to compress.</p>

<p>The adjusted compression ratio for the compressed disk is 188.62GB/40.9GB is 4.61, while the zeroed out disk is 128GB/15.2GB = 8.421.</p>

<p>This doesn't tell us a whole lot, but confirms that the disk with deleted files doesn't compress as well as the one with just zeros. Since the deleted file was a subsection of the files (potentially gzipped), it makes sense that the compression ratio would be smaller.</p>

<h3 id="areas-for-improvement">Areas for improvement</h3>

<ul>
  <li>
    <p>1.2x-2x faster 4k random reads using multiple threads from CrystalDiskMark (Q32T16.) Unclear if this will be a performance improvement under WSL2.</p>
  </li>
  <li>
    <p>The sampling method also includes the internal log, which is 32767 blocks or about 17MB. We probably shouldn't include the log in our sampling because that's not considered a deleted file. While the internal log is very small compared to our entire disk, larger disks might have a larger log and could affect sampling.</p>
  </li>
  <li>
    <p>I zeroed the disk using cat /dev/zero \&gt; zeros then deleted the file; this means that each block doesn't have a special header because if it did, then that header should be intact for all of the blocks. Since deleting the file was instantaneous, a header couldn't have been on every block, and this would have caused our free block count to be close to zero. This means that a single large file with just zeros that was deleted would not count towards the deleted files.</p>
  </li>
  <li>
    <p>Consequently, if a file has zero blocks that align with the filesystem's blocks, then that part of the file won't be counted towards the deleted data total.</p>
  </li>
  <li>
    <p>fstrim could improve the performance of discarding unused blocks <a href="https://man7.org/linux/man-pages/man8/fstrim.8.html">https://man7.org/linux/man-pages/man8/fstrim.8.html</a>. It has a minimum option that could be used in conjunction with the free space extents histogram discussed earlier to determine a tradeoff between how many unused blocks will be discarded compared to how long sequential writes will take.</p>
  </li>
</ul>

<h3 id="areas-to-explore-in-the-future">Areas to explore in the future</h3>

<p>A drive does not have to contain pure zeros for it to have been wiped. It is possible for a drive to contain any amount of random data, a random pattern, or a predictable pattern, as long as it is not sensitive information. The method described in this post can't be adapted to work in the general case because it works on blocks of content rather than finding sensitive information.</p>

<p>While these are interesting questions, they are outside the scope of this post.</p>

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

<p>Using block-level analysis, we determined how many unused blocks there are compared to those that have already been discarded. We were able to estimate how much data had been deleted but had not yet been reallocated.</p>

<p>We found out that our first filesystem had about 60.62GB ± 1% of deleted data, and the identical filesystem which was zeroed out and defragged had about 0.26MB ± 1% of deleted data. While I don't know for certain whether these are actually deleted files, the two measurements are different enough to warrant a conclusion.</p>

<p>While the apparent size of the recovered files by “du” was about 45GB, it gives us the same order of magnitude as to how many files were deleted. Additionally, it is unclear if photorec does not recover files that don't have a valid file header (as it uses file types to determine what files to recover.)</p>

<p>This measurement is important because of:</p>

<ul>
  <li>Potential security concerns (deleted files can be salvaged.) This method should not be used in and of itself for checking if any deleted files remain because it is stochastic, but it is helpful in preemptively prioritizing what drives have many deleted files or large quantities of deleted data.</li>
  <li>Determining when or what threshold to zero out a disk's free space. Doing it too often is wasteful, but not often enough means the underlying storage may be occupying unnecessary data. This is especially relevant for dynamic disks.</li>
  <li>How compressible your disk is if you're sending it over a network. Getting the average compressibility of all files may not be a good estimator of disk compressibility because the deleted files may still take up space.</li>
</ul>

<h3 id="footnotes">Footnotes</h3>

<p>0: This data is from <code class="language-plaintext highlighter-rouge">/dev/urandom</code>.</p>

<p>1: In the context of this post, zero blocks are free space.</p>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[How to estimate deleted-but-not-overwritten data by scanning unused filesystem blocks, with an XFS example and recovery-tool walkthrough.]]></summary></entry><entry><title type="html">Automatically expanding code review scope with suggested non changed files</title><link href="https://alexyorke.github.io/2021/03/10/automatically-expanding-code-review-scope-with-suggested-non-changed-files/" rel="alternate" type="text/html" title="Automatically expanding code review scope with suggested non changed files" /><published>2021-03-10T00:00:00+00:00</published><updated>2021-03-10T00:00:00+00:00</updated><id>https://alexyorke.github.io/2021/03/10/automatically-expanding-code-review-scope-with-suggested-non-changed-files</id><content type="html" xml:base="https://alexyorke.github.io/2021/03/10/automatically-expanding-code-review-scope-with-suggested-non-changed-files/"><![CDATA[<p>Code review is important because it reduces coding errors when introducing new code into the codebase. It can also increase code quality. However, conventional diffs suffer from a flaw if they are solely used as code review tools: <strong>they don’t show what should have been changed.</strong></p>

<p>There’s two main ways that files are changed together:</p>

<ul>
  <li>
    <p>Through refactoring, renaming, or logic changes.</p>
  </li>
  <li>
    <p>Renaming a string or term.</p>
  </li>
</ul>

<p>Renaming a string or a team in a string won’t be covered by this post, and this approach isn’t that great for solving it. This is because renaming strings may encompass many files at once but only once, and so there won’t be enough data to correlate them together.</p>

<h3 id="similar-research">Similar research</h3>

<p>There has been research similar to this, including <a href="https://sail.cs.queensu.ca/Downloads/MSR2015_InvestigatingCodeReviewPracticesInDefectiveFiles_AnEmpiricalStudyOfTheQtSystem.pdf">[future-defective]{.ul}</a> files and <a href="http://chakkrit.com/assets/papers/thongtanunam2015saner.pdf">[Who Should Review My Code? A File Location-Based Code-Reviewer Recommendation Approach for Modern Code Review]{.ul}</a>. What we need is the ability to find out what files are normally changed with other files, then flag those if they aren’t changed as part of a PR. This article <a href="https://dzone.com/articles/temporal-correlation-git">[Temporal correlation in Git repositories]{.ul}</a> shows a sample program written in PHP that correlates file paths with other file paths if they are changed together.</p>

<p>While that article is focused around avoiding <a href="https://michaelfeathers.typepad.com/michael_feathers_blog/2011/09/temporal-correlation-of-class-changes.html">[Shotgun Surgery]{.ul}</a> (finding classes which are correlated together too much) it can also be used to find which files should be modified together. Sometimes it’s not possible to fully decouple files (e.g. when adding a translation string when modifying the UI.)</p>

<h3 id="getting-started">Getting started</h3>

<p>Let’s experiment. Clone <a href="https://github.com/angular/angular">[Angular]{.ul}</a>, create a directory called “commits”, cd into angular, and run this command:</p>

<p>while read hash; do git show --pretty='format:' --name-only "$hash" &gt; ../commits/$RANDOM$RANDOM$RANDOM.txt; done &lt; &lt;(git log --oneline | cut -d " " -f 1)</p>

<p>What this command does (it’s from the original article) is get a list of all of the file paths that changed for each commit and create a new file with those file paths in it. The command isn’t exactly optimal; the random file names aren’t guaranteed to be perfectly random and so there might be a chance that one of the files gets overwritten. For demo purposes this is ok, as there’s over 20k files so if one gets overwritten it’s not the end of the world. I could use a counter next time.</p>

<p>Whipping up a quick C# program, we can group the files by the other files that they’ve changed with. I don’t want to release the source code just yet as it’s an O(N\^2) algorithm and the code is super messy. Essentially what we’re doing is reading each file with the file paths, creating a key-value pair with each path together with each other path (permutations), and then adding those pairs to a list.</p>

<p>With those pairs, we group by the first element and then group the values together. This is what we get:</p>

<p>Type = "packages/router/src/url_tree.ts" -&gt; [0] = { File = "packages/router/src/router_state.ts", Count = 15 }, [1] = { File = "packages/router/src/shared.ts", Count = 15 }, [2] = { File = "packages/router/src/router.ts", Count = 14 }, …</p>

<h3 id="parsing-the-output">Parsing the output</h3>

<p>How to read: out of all the PRs that the file at the path packages/router/src/url_tree.ts was changed, the file at the path packages/router/src/router_state.ts was also changed in the same PR 15 times this file was changed, the file at the path packages/router/src/shared.ts was changed 15 times, etc. If we change url_tree.ts without changing router_state.ts, then there might be a bug, because router_state.ts is usually changed when url_tree.ts is changed.</p>

<p>Note that there are some caveats with this approach. I’m not checking when url_tree.ts changes just by itself, and I don’t know the percentage of how often router_state.ts changes in regards to the other files. Also, I’m not keeping track of renaming.</p>

<p>If we repeat this for all of the commits, we can get this structure for each file path. I did the first 100 files on Angular and got a 12MB JSON file; there’s certainly a better way to store the database, however.</p>

<h3 id="what-this-means-for-you">What this means for you</h3>

<p>What this means is that if you have a PR with file X changed, you can look it up in a dictionary whose value is a list of the other popular files that are usually changed with this file. If those files have a high probability of being changed (say, 90%), then you could warn the user that these files are usually changed together and that it was “missing” from the PR. Of course, the files aren’t usually inextricably linked but it’s good to do a double check.</p>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[A technique for expanding code reviews beyond the diff by suggesting related files that usually change together, using commit-history correlation.]]></summary></entry><entry><title type="html">End to end tests</title><link href="https://alexyorke.github.io/2021/03/10/github-actions-e2e-testing-angular/" rel="alternate" type="text/html" title="End to end tests" /><published>2021-03-10T00:00:00+00:00</published><updated>2021-03-10T00:00:00+00:00</updated><id>https://alexyorke.github.io/2021/03/10/github-actions-e2e-testing-angular</id><content type="html" xml:base="https://alexyorke.github.io/2021/03/10/github-actions-e2e-testing-angular/"><![CDATA[<p>E2E (end-to-end) tests. Angular. Yes, they’re flaky. Yes, they’re slow. But they are very useful and potentially under used. They’re sometimes pushed off to run on end-developers devices because getting it to work on the CI is too finicky.</p>

<p>Why are E2E tests ignored? Well, they’re a bit of a pain to get running in general, especially for newer users. (The command npm run test should be called npm run test-components, because it’s just running the component tests, by default.)</p>

<p>Just a simple E2E test that checks a page title is incredibly useful. Sadly, it does need a lot of setup to just do a simple E2E test so oftentimes it goes ignored.</p>

<h3 id="starting-from-scratch">Starting from scratch</h3>

<p>Let’s make a brand new Angular project. From scratch. From ng new my-app. Let’s run npm install for good measure, then run npm run e2e.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng new my-app

npm install

git init

git add .

git commit -m "Initial commit"

npm run e2e
</code></pre></div></div>

<p>You may think that Protractor is part of Angular. It’s not--it’s a separate project. This means that the command npm run e2e doesn’t work out of the box (yes, I have the Google Chrome browser installed):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\[19:33:27\] I/launcher - Running 1 instances of WebDriver

\[19:33:27\] I/direct - Using ChromeDriver directly\...

DevTools listening on ws://127.0.0.1:56681/devtools/browser/829c0ca6-bc1d-4f9c-aa8f-ba379fca9279

\[50252:41332:0309/193337.329:ERROR:device_event_log_impl.cc(214)\] \[19:33:37.329\] USB: usb_device_handle_win.cc:1056 Failed to read descriptor from node connection: A device attached to the system is not functioning. (0x1F)

\[50252:41332:0309/193337.362:ERROR:device_event_log_impl.cc(214)\] \[19:33:37.363\] USB: usb_device_handle_win.cc:1056 Failed to read descriptor from node connection: A device attached to the system is not functioning. (0x1F)

\[50252:41332:0309/193337.386:ERROR:device_event_log_impl.cc(214)\] \[19:33:37.386\] USB: usb_device_handle_win.cc:1056 Failed to read descriptor from node connection: A device attached to the system is not functioning. (0x1F)

Jasmine started

workspace-project App

× should display welcome message

\- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

internal/timers.js:549:17

jasmine-spec-reporter: unable to open \'internal/timers.js\'

Error: ENOENT: no such file or directory, open \'internal/timers.js\'

internal/timers.js:492:7

jasmine-spec-reporter: unable to open \'internal/timers.js\'

Error: ENOENT: no such file or directory, open \'internal/timers.js\'

\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*

\* Failures \*

\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*

1\) workspace-project App should display welcome message

\- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

Executed 1 of 1 spec (1 FAILED) in 35 secs.

\[19:34:36\] I/launcher - 0 instance(s) of WebDriver still running

\[19:34:36\] I/launcher - chrome \#01 failed 1 test(s)

\[19:34:36\] I/launcher - overall: 1 failed spec(s)

\[19:34:36\] E/launcher - Process exited with error code 1

npm ERR! code 1

npm ERR! path C:\\Users\\yorke\\Desktop\\angular-tour-of-heroes-master\\angular-tour-of-heroes-master

npm ERR! command failed

npm ERR! command C:\\WINDOWS\\system32\\cmd.exe /d /s /c ng e2e

npm ERR! A complete log of this run can be found in:

npm ERR! C:\\Users\\yorke\\AppData\\Local\\npm-cache\\\_logs\\2021-03-10T00_34_37_674Z-debug.log
</code></pre></div></div>

<p>Side-note: ok, so in this case I was “lucky” that it failed. Turns out if you have Docker running and a few other things running, your computer might get a bit slow (surprise surprise.) This causes the test to timeout. This is still an error though. Sometimes it works, sometimes it doesn’t. On the CI though it was consistently failing, so we’ll go with that.</p>

<p>Hmm, ok, maybe our setup is weird. Let’s run it via GitHub actions instead (and add it manually to the build + test stage, since the first few in Google’s search results don’t contain E2E tests by default.) What happens now?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\[00:40:26\] I/launcher - Running 1 instances of WebDriver

\[00:40:26\] I/direct - Using ChromeDriver directly\...

\[00:40:29\] E/runner - Unable to start a WebDriver session.

\[00:40:29\] E/launcher - Error: WebDriverError: unknown error: Chrome failed to start: exited abnormally.

(unknown error: DevToolsActivePort file doesn\'t exist)

(The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)

(Driver info: chromedriver=89.0.4389.23 (61b08ee2c50024bab004e48d2b1b083cdbdac579-refs/branch-heads/4389@{\#294}),platform=Linux 5.4.0-1040-azure x86_64)

at Object.checkLegacyResponse (/home/runner/work/angular-tour-of-heroes/angular-tour-of-heroes/node_modules/selenium-webdriver/lib/error.js:546:15)

at parseHttpResponse (/home/runner/work/angular-tour-of-heroes/angular-tour-of-heroes/node_modules/selenium-webdriver/lib/http.js:509:13)

at /home/runner/work/angular-tour-of-heroes/angular-tour-of-heroes/node_modules/selenium-webdriver/lib/http.js:441:30

at processTicksAndRejections (internal/process/task_queues.js:97:5)

\[00:40:29\] E/launcher - Process exited with error code 100

npm ERR! code ELIFECYCLE

npm ERR! errno 1

npm ERR! angular-tour-of-heroes\@0.0.0 e2e: \`ng e2e\`

npm ERR! Exit status 1

npm ERR!

npm ERR! Failed at the angular-tour-of-heroes\@0.0.0 e2e script.

npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
</code></pre></div></div>

<p>At this point you’re likely to want to fix the error, so you’ll do one or some of the following (potentially, unless you know where I’m going with this):</p>

<h3 id="option-one">Option one</h3>

<p>Delete the E2E tests or delete the E2E test runner.</p>

<p>It can be tempting to snip the test that looks like it’s just checking if one equals one...</p>

<p>it('should display welcome message', async () =&gt; {</p>

<p><strong>await page.navigateTo();</strong></p>

<p><strong>expect(await page.getTitleText()).toEqual('&lt;%= relatedAppName %&gt; app is running!');</strong></p>

<p>});</p>

<p>...Just remove the bolded statement and all of the errors magically disappear, except we’re not running the E2E tests. But who cares? All it’s checking is the page title. Right?</p>

<h3 id="option-two">Option two</h3>

<p>Try to get the E2E tests working or die trying. I’m being a bit sarcastic, but I’ll work through the problem as if I sequentially Googled each error message. It is surprisingly difficult.</p>

<p>Search Google for “Error: WebDriverError: unknown error: Chrome failed to start: exited abnormally.” then…</p>

<ul>
  <li>
    <p>Click on <a href="https://github.com/angular/webdriver-manager/issues/444">[https://github.com/angular/webdriver-manager/issues/444]{.ul}</a>, but that doesn’t go anywhere</p>
  </li>
  <li>
    <p>Click on <a href="https://github.com/actions/virtual-environments/issues/41">[https://github.com/actions/virtual-environments/issues/41]{.ul}</a>, which goes to <a href="https://github.com/actions/virtual-environments/issues/9">[https://github.com/actions/virtual-environments/issues/9]{.ul}</a>, which suggests to do “apt-get install chromium-chromedriver” first, so you squeeze in a “apt-get update &amp;&amp; apt-get install chromium-chromedriver” before the npm build</p>
  </li>
  <li>
    <p>That gives you the same error.</p>
  </li>
  <li>
    <p>You go to <a href="https://github.com/heroku/heroku-buildpack-google-chrome/issues/56">[https://github.com/heroku/heroku-buildpack-google-chrome/issues/56]{.ul}</a> which has seven ways to try to fix it with varying degrees of success.</p>
  </li>
  <li>
    <p>You then search “E2E google chrome github actions”.</p>
  </li>
  <li>
    <p>The first five results are unrelated to anything to do with github actions.</p>
  </li>
  <li>
    <p>You then find <a href="https://stackoverflow.com/questions/63651059/">[https://stackoverflow.com/questions/63651059/]{.ul}</a> which says to run the same command that you tried except prefixed with sudo.</p>
  </li>
  <li>
    <p>You then copy-and-paste the exact 12-line YAML part/blob of text of the E2E test from the answer into your workflow.</p>
  </li>
  <li>
    <p>It doesn’t work because it says permission denied.</p>
  </li>
  <li>
    <p>You try to prefix every apt-get with sudo.</p>
  </li>
  <li>
    <p>You get an error like “E: This command can only be used by root.”</p>
  </li>
  <li>
    <p>At this point you may delete the E2E test and just forget it. Or you could keep going and wonder if E2E tests were designed to be run.</p>
  </li>
  <li>
    <p>You find <a href="https://askubuntu.com/questions/845534/this-command-can-only-be-used-by-root">[https://askubuntu.com/questions/845534/this-command-can-only-be-used-by-root]{.ul}</a> and then prefix “apt-key” with sudo</p>
  </li>
  <li>
    <p>Try it again.</p>
  </li>
  <li>
    <p>You get a new error, “/home/runner/work/_temp/878817f4-7ca2-4e31-8c02-092beb2da616.sh: line 5: /etc/apt/sources.list.d/google.list: Permission denied”</p>
  </li>
  <li>
    <p>You add sudo to wget and to echo just in case.</p>
  </li>
  <li>
    <p>Try again.</p>
  </li>
  <li>
    <p>Get the same error.</p>
  </li>
  <li>
    <p>Oops. Shouldn’t have added sudo in front of echo.</p>
  </li>
  <li>
    <p>Try again.</p>
  </li>
  <li>
    <p>Still with me? Want to give up now?</p>
  </li>
  <li>
    <p>Oh no, same error.</p>
  </li>
  <li>
    <p>You do some more searching and find out that &gt;&gt; isn’t as root. You then use “sudo tee -a” (because &gt;&gt; doesn’t append as root). Try again.</p>
  </li>
  <li>
    <p>The same error again this time around, “Error: WebDriverError: unknown error: Chrome failed to start: exited abnormally.”</p>
  </li>
  <li>
    <p><strong>Somehow you add this to your protractor.conf.js:</strong></p>
  </li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chromeOptions: {
args: ['--headless']
}
</code></pre></div></div>

<p>All of a sudden it works!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\[20:31:04\] W/element - more than one element found for locator By(css selector, app-root .content span) - the first result will be used

workspace-project App

√ should display welcome message

Executed 1 of 1 spec SUCCESS in 2 secs.

\[20:31:05\] I/launcher - 0 instance(s) of WebDriver still running

\[20:31:05\] I/launcher - chrome \#01 passed
</code></pre></div></div>

<h3 id="recap-and-final-script">Recap and final script</h3>

<p>Let’s recap what we did. Here’s part of the original script, modified a little and commented a lot.</p>

<p># Update our apt repos, then install wget. You don’t <em>need</em> wget (you could use curl); it doesn’t matter though; you just need to download a signing key.</p>

<p>sudo apt-get update</p>

<p>sudo apt-get install -y wget</p>

<p># To get Google Chrome, you have to add the PPA for it. Google Chrome isn’t in the default Ubuntu repo. You can run apt-get install wget because wget is in the repos.</p>

<p>wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -</p>

<p>echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee -a /etc/apt/sources.list.d/google.list</p>

<p># After adding in a repo, you have to tell apt to fetch the manifests. This includes Google Chrome.</p>

<p>sudo apt-get -y update</p>

<p># Install Chrome.</p>

<p>sudo apt-get install -y google-chrome-stable</p>

<p># “Run” Google Chrome, but with the version flag. This produces a version with a lot of verbose output. We just care about the first line and we can use a regex to extract just the version number.</p>

<p>VERSION=`google-chrome --version | egrep -o '[0-9]+.[0-9]+' | head -1'</p>

<p>The script goes on to install webdriver-manager and a chrome package. Turns out that we don’t need them as it “uses Chromedriver directly.” It might have been for older Chrome versions. The test passes perfectly fine without them.</p>

<p>Just this isn’t enough. We have to tell Protractor and Chrome that we’re running in headless mode, which means that we can’t run a UI. We also need to update Protractor. Let’s add,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>capabilities: {
browserName: 'chrome',
chromeOptions: {
args: ['--headless']
}
},
</code></pre></div></div>

<p><a href="https://www.protractortest.org/#/browser-setup">[To protractor.conf.js]{.ul}</a>. Pay attention to the “args” that we’ve added. We’re adding these arguments because Chrome doesn’t know we’re running in a container. This means it tries to set up a UI and such and it can’t do it because there isn’t a UI.</p>

<p><strong>Why don’t we need --no-sandbox?</strong> Some of the older issues mention it, but it seems there were some changes to chrome which allows chrome to run with the sandboxing enabled within a container. This stemmed from an <a href="https://docs.travis-ci.com/user/chrome#karma-chrome-launcher">[error on Travis CI]{.ul}</a> about “The SUID sandbox helper binary was found, but is not configured correctly”. Using --no-sandbox is a quick band aid patch. The correct way is through <a href="https://github.com/electron/electron/issues/17972">[setting the owner of the binary to root and the permissions correctly.]{.ul}</a></p>

<p><strong>You don’t need --disable-dev-shm-usage anymore either.</strong> You do need --headless though, as Chrome doesn’t know we’re on a CI.</p>

<p>Now, --no-sandbox, --disable-dev-shm-usage and some other flags may be required for older versions of Chrome. Keep that in mind while testing.</p>

<h3 id="why-are-e2e-tests-important">Why are E2E tests important?</h3>

<p>Maybe this should have been the first section. If we were to snip our first E2E test that <em>appeared as though</em> it wasn’t doing anything, we’d be missing out on a lot of testing opportunities. The first test isn’t a tautology. This point two fold:</p>

<ul>
  <li>
    <p>A single E2E test causes the afterEach function to run, which fails the tests if the console contains any errors. The afterEach function will not run if there are no tests. The console can contain errors (and still pass the component tests.) <strong>Try adding some bad code to the main.po.ts file. The component tests will run fine, but your app will just be a blank page. npm run test isn’t enough to check if your app works.</strong></p>
  </li>
  <li>
    <p>“Breaks in” E2E testing. If you want to write another E2E test, go for it! You know that everything is in a somewhat sane state because you’re able to run a single test and it works fine. Granted, there might be other problems down the line if you’re working with flakier tests but otherwise the dependencies are installed.</p>
  </li>
</ul>

<h3 id="what-if-i-want-to-test-with-extensions-including-chrome-web-store-ones">What if I want to test with extensions, including Chrome Web Store ones?</h3>

<p>Chrome with the --headless flag doesn’t support extensions yet. You <a href="https://stackoverflow.com/questions/45372066/is-it-possible-to-run-google-chrome-in-headless-mode-with-extensions">[have to use xvfb]{.ul}</a>, which is a virtual frame buffer. You may also want to pass in the “-a” flag if you’re <a href="https://manpages.debian.org/testing/xvfb/xvfb-run.1.en.html">[running xvfb-run]{.ul}</a> with multiple concurrent instances on the same machine.</p>

<p>It’ll want a path to your extension, so <a href="https://superuser.com/questions/290280/how-to-download-chrome-extensions-for-installing-in-another-computer">[you’ll have to download it first]{.ul}</a> to your computer from the Chrome Web Store, then add it to your Git repo.</p>]]></content><author><name>Alex Yorke</name></author><summary type="html"><![CDATA[How to get Angular end-to-end tests running in GitHub Actions, including setup gotchas and tips for dealing with slowness and flakiness.]]></summary></entry></feed>