<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>J. Gulan Blog</title>
        <link>https://jgulan.dev/blog</link>
        <description>J. Gulan Blog</description>
        <lastBuildDate>Sat, 19 Apr 2025 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[LLMs and an optimistic take on the future of software engineering]]></title>
            <link>https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering</link>
            <guid>https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering</guid>
            <pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Large Language Models (LLMs) as an amplifying lever for software engineers rather than a replacement for them.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p><strong>“Give me a lever long enough and a fulcrum on which to place it, and I shall move the world.”</strong>
— a quote attributed to Greek mathematician and physicist, Archimedes.</p>
</blockquote>
<img align="left" src="https://jgulan.dev/assets/images/archimedes-lever-7422d72f6bbd7d6ac6968413ac314068.jpg" alt="Archimedes lifting the world">
<p><em>Archimedes' lever (image credit <sup><a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#user-content-fn-1-7d7ee3" id="user-content-fnref-1-7d7ee3" data-footnote-ref="true" aria-describedby="footnote-label" class="anchorTargetStickyNavbar_Vzrq">1</a></sup>)</em></p>
<p>I vividly recall my early days of programming: the dopamine hits after seeing my code successfully compile and my program run;
the thing that I imagined suddenly coming to life on screen. Programming was difficult and knowledge hard-won, perhaps due to my
skill level at the time and the fact that documentation and guidance were not so readily available.</p>
<p>These were the days of 56k dial-up, GeoCities and Quake III Arena. Websites were individually-crafted and online communities were
mostly decentralised. Programming knowledge was dispersed across small and dedicated websites and communities. Then came the rapid
proliferation of open-source software and Stack Overflow's arrival, which changed the scene again, centralising programming
discussions, better disseminating knowledge and example code.</p>
<p>The world looks very different now and the landscape is shifting dramatically once more. A pivotal moment arrived in 2022 with the
release of <a href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer" class="">ChatGPT</a>, the generative artificial intelligence (AI) chatbot from OpenAI, which is based on their Generative Pre-trained
Transformer (GPT) Large Language Model (LLM).</p>
<p>While the initial implications might have been hazy, with each subsequent advancement, the power of LLMs and their place in our
future is becoming clearer.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cognitive-bias-and-the-creative-identity-crisis">Cognitive bias and the creative identity crisis<a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#cognitive-bias-and-the-creative-identity-crisis" class="hash-link" aria-label="Direct link to Cognitive bias and the creative identity crisis" title="Direct link to Cognitive bias and the creative identity crisis" translate="no">​</a></h2>
<p>The <a href="https://en.wikipedia.org/wiki/AI_effect" target="_blank" rel="noopener noreferrer" class="">"AI effect"</a> describes how, once AI researchers accomplish a task previously considered indicative
of true AI—such as defeating a human at chess—the definition of genuine AI shifts, often relegating the achievement to mere
logic or "just maths".</p>
<p>I think part of this stems from a potential creative identity crisis and a tendency to want to hold onto skills that feel uniquely ours.
Imagine you've spent your life mastering a craft or some creative art: a studio photographer, a professional song composer, a podcaster, or a writer.
Generative AI can create incredible photos, incorporating subjects (products, people, etc.) and beautiful, lifelike videos, resulting in AI-generated
adverts and fairly convincing movies (see <a href="https://vimeo.com/1047370252" target="_blank" rel="noopener noreferrer" class="">Kitsune</a>). It can create music of all different styles, write academic literature
reviews, poetry and stories. It can create convincing podcasts with multiple hosts who sound <em>real</em> and discuss source materials of your choice (like <a href="https://jgulan.dev/assets/files/jgulan-llms-and-an-optimistic-take-on-the-future-of-software-engineering-9ad04cea80224260e84720e1af7b95c7.mp3" target="_blank" class="">for this article</a>, courtesy of Google's NotebookLM).</p>
<p>“AI slop”, you say? Perhaps sometimes but the power and quality of generative AI is advancing quickly, to the point where it is a challenge
to keep up, and we are still working out how to make best use of it. Progress is set to continue given the incredible competition
between the key players, like OpenAI, Google, Meta AI and Anthropic. We're no longer rendering human hands that look like spaghetti and
with too many fingers.</p>
<p>A common argument is that LLMs are just statistical models, predicting the token that is most likely to follow the previous
tokens, over and over. Behind the glossy chat interface, the main ChatGPT API method is called Chat Completions (e.g. <code>POST https://api.openai.com/v1/chat/completions</code>), which has connotations of predictive text completion more than real intelligence.
This is true but misses the point and does not diminish the utility of LLMs.</p>
<p>In fact, the brain and LLMs work in somewhat similar ways, both by forming connections. In our brains, it is neurons communicating
and adapting based on learning and experience. In LLMs, it is a vast number of parameters that are adjusted during training, based
on huge amounts of data. When we prompt an LLM, we traverse these connections, like the sparks of electrical activity that dance
across our brain's neurons.</p>
<p>As humans, we can dream up amazing innovations and novel ideas with <em>true soul</em>, but how much of what we produce is truly original or
innovative? Many songs are composed of the <a href="https://en.wikipedia.org/wiki/List_of_chord_progressions" target="_blank" rel="noopener noreferrer" class="">same key chord progressions</a> and
only occasionally do we hear a song that truly stands out. Film scripts often repeat the same tried and tested storylines. Taking these
"building blocks", whether these are notes or audio frequencies that form melodies, pixels that form images, words and characters that form
source code or a natural language, and knowing how to arrange them to achieve a particular goal or effect is often what matters. And that is what
generative AI seeks to do.</p>
<p>We need to remain mindful of these cognitive biases and emotional responses when approaching new developments in the
field of AI, otherwise we risk overlooking or misunderstanding an incredibly useful technology.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="llms-and-software-engineers">LLMs and software engineers<a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#llms-and-software-engineers" class="hash-link" aria-label="Direct link to LLMs and software engineers" title="Direct link to LLMs and software engineers" translate="no">​</a></h2>
<p>It turns out LLMs can write code and they are reasonably good at it, when provided the right prompt and context. If your role is
to take on small and very well defined tickets and simply translate these into small code changes, then your job is ripe for
disruption.</p>
<p>If we feed an LLM a prompt with the relevant context—parts of or all of the codebase—and clear instructions like:
<em>“Add a new ‘birth_date’ database column to the ‘users’ table of the Date type, which is nullable and has no index”</em>, then our
LLM-driven agent will probably produce the correct result and will even raise a pull request.</p>
<p>I notice a general trend of pessimism in online communities, particularly LinkedIn and X (Twitter), where we are told by
influencers, not engineers, that this is the end of programming and software engineering. These comments tend to attract
a lot of attention and clicks, devolve into arguments and generate a lot of circular discussion and noise, which is probably
their intended purpose. I have a different and more optimistic take.</p>
<p>Much like the cranking of Archimedes' imaginary lever, LLMs let us amplify human effort to produce superhuman results and for
everyone, and especially software engineers, this is an incredible time to be alive.</p>
<p>As software engineers, we already had great leverage. We can create and deploy an application or service and affect millions or
billions of users, creating huge value for businesses and individuals and potentially changing the world. Armed with an LLM,
individual software engineers and teams can now move even faster. Even better, and much as I enjoy software development, we can offload
some of the drudgery and tedious work that we battle and that takes our focus away from what really matters. Maintenance work
like fixing breaking API changes after a package upgrade might fall entirely to an LLM-based agent, and we can focus on the more
exciting, novel and meaningful work.</p>
<p>There has also never been a better time to start a side hustle. The LLM will empower software engineers to start one-person
companies and exceptional engineers will now produce even more exceptional results. Some of these companies will disrupt the
slow-moving incumbent companies, operating with leaner teams and tighter margins, increasing competition all around.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="renewing-our-focus">Renewing our focus<a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#renewing-our-focus" class="hash-link" aria-label="Direct link to Renewing our focus" title="Direct link to Renewing our focus" translate="no">​</a></h2>
<p>With one bottleneck reduced—the gap between the concepts and design in our minds and the resulting source code on our
screens—we must shift our focus to hone our other skills and to tackle the remaining bottlenecks, which include:</p>
<ul>
<li class="">collaboration, communication, leadership and other soft skills</li>
<li class="">business and domain knowledge</li>
<li class="">product and systems thinking</li>
<li class="">critical thinking and problem solving</li>
<li class="">problem decomposition</li>
<li class="">deep technical understanding across the stack</li>
<li class="">prototyping, creativity and innovation</li>
<li class="">testing and test automation</li>
<li class="">debugging</li>
<li class="">optimisation</li>
<li class="">data management and analysis</li>
</ul>
<p>And we are fortunate that LLMs can support us with these areas too.</p>
<p>For engineers, LLMs are an amplifier and multiplier. They are a <a href="https://web.archive.org/web/20250212011320/https://signalvnoise.com/svn3/provide-sharp-knives/" target="_blank" rel="noopener noreferrer" class="">"sharp knife"</a>, perhaps the sharpest of all, and we can achieve
so much with them, but we must be careful not to cut our fingers. Engineering fundamentals matter now more than ever.</p>
<p>Systems design and software architecture mattered before but it now matters more because poor architecture will now sooner lead to bloat,
duplication, complexity and poor performance. Thorough and constructive peer reviews mattered before but now matter more, especially if we
are moving at a faster pace and changing or adding more lines of code. An awareness of security vulnerabilities always mattered but now
matters more as we move to more rapidly ship new features, relying on code that might not have been written entirely by humans. If we forget the fundamentals,
we risk <a href="https://en.wikipedia.org/wiki/Vibe_coding" target="_blank" rel="noopener noreferrer" class="">vibe coding</a> ourselves into a corner where things start to fall apart.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="llms-replacing-software-engineers">LLMs replacing software engineers<a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#llms-replacing-software-engineers" class="hash-link" aria-label="Direct link to LLMs replacing software engineers" title="Direct link to LLMs replacing software engineers" translate="no">​</a></h2>
<p>In an <a href="https://youtu.be/7k1ehaE0bdU?si=BcKXwkmJC2E4dqw-&amp;t=7697" target="_blank" rel="noopener noreferrer" class="">interview with Joe Rogan</a>, Mark Zuckerberg of Meta recently stated:</p>
<blockquote>
<p><em>“Probably in 2025, we at Meta […] are going to have an AI that can effectively be a sort of mid-level engineer that you have at your
company, that can write code.”</em></p>
</blockquote>
<p>Although apparently controversial, Zuckerberg's comment is similar to my point above regarding the translation of a small and well
defined Jira task into code changes. He does not actually state that engineers will be replaced with agents, even if some find
that idea attractive. LLM-based agents lack autonomy and agency and require direction and supervision. Let us not forget that
writing a detailed prompt to produce source code is still programming of a sort, albeit at a higher level of abstraction. Churning
out code isn't enough - we still need the right code and it is up to us to decide what right is. Zuckerberg added, "I think [it]
will augment the people working on it".</p>
<p>In my opinion, LLMs will not replace software engineers on the whole, but will give existing software engineers far greater
leverage. A much longer lever to produce greater torque at the fulcrum, if you like, greatly amplifying the initial inputs.</p>
<p>There is a caveat though. I suspect that LLMs will form a component of Artificial General Intelligence (AGI),
or will at least act as a stepping stone towards it, assuming AGI can indeed be created. If AGI arrives, then <em>all bets are off</em> and we enter an entirely
new paradigm. In this future, no job is safe from disruption. With robotics, even physical, hands-on jobs will eventually be able
to be automated. There is a possible future where a robot lays bricks, rewires your home and carries in your groceries. I digress.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-types-of-companies">Two types of companies<a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#two-types-of-companies" class="hash-link" aria-label="Direct link to Two types of companies" title="Direct link to Two types of companies" translate="no">​</a></h3>
<p>Amongst others, there will be two key types of companies. The first type will be those that see software development simply as an overhead.
They are not seeking meaningful growth or to push the boundaries, and are therefore able to now achieve the same output with fewer engineers.
If profit is equal to total revenue minus total expenses, then reducing engineering headcount expenses will in turn increase profit, satisfying
the short-term mindset.</p>
<p>On the other hand, the second type of company will leverage LLMs to significantly multiply their engineers' output, using the
productivity boost to outmanoeuvre the competition, building better products and features, and many more of them. Combining human ingenuity
at scale with LLMs, these companies will run rings around the first type of company.</p>
<p>For software engineers, some turbulence lies ahead as companies begin to experiment with and adapt to LLMs, and we see the
tension between the two types of companies that I described above play out. Jobs will be lost, others will change and new jobs
will be created. With the help of LLMs, some displaced engineers who would never have considered starting their own businesses
will leave established companies to do just that.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Overall, I remain extremely optimistic about software engineering. Software is continuing to <a href="https://web.archive.org/web/20250410012411/https://a16z.com/why-software-is-eating-the-world" target="_blank" rel="noopener noreferrer" class="">"eat the world"</a> and there is no sign of
this stopping. Software will one day be everywhere and in every thing and will be more critical to our lives, and we will need people
who can build, understand, debug and maintain software. This is <a href="https://en.wikipedia.org/wiki/Jevons_paradox" target="_blank" rel="noopener noreferrer" class="">Jevons paradox</a> in action. As software becomes
easier to build, we will probably want more of it and more people to develop and manage it.</p>
<p>In fact, there is already plenty of work ahead - almost all of our existing software and its interfaces are built on the fundamental
assumption that computers require imperative instructions, but that is no longer the case. We can talk to computers and they can
talk back. We have the huge opportunity to build new systems, experiences and products using AI as a foundation. Similar
opportunities appeared during the smartphone revolution, which literally put powerful computers in people's pockets - we had to
rethink the assumption that computers stayed at home and rebuild accordingly. Those that adapted and took advantage of
these opportunities were ultimately the winners. Let's get to work.</p>
<!-- -->
<section data-footnotes="true" class="footnotes"><h2 class="anchor anchorTargetStickyNavbar_Vzrq sr-only" id="footnote-label">Footnotes<a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#footnote-label" class="hash-link" aria-label="Direct link to Footnotes" title="Direct link to Footnotes" translate="no">​</a></h2>
<ol>
<li class="anchorTargetStickyNavbar_Vzrq" id="user-content-fn-1-7d7ee3">
<p>Credit for the image to ZDF, Terra X, Gruppe 5, Susanne Utzt, Cristina Trebbi, Jens Boeck, Dieter Stürmer, Fabian Wienke,
Sebastian Martinez, which has been cropped and is licensed under the
<a href="https://creativecommons.org/licenses/by/4.0/deed.en" target="_blank" rel="noopener noreferrer" class="">Creative Commons Attribution 4.0 International license</a>. <a href="https://jgulan.dev/blog/2025/04/19/llms-and-an-optimistic-take-on-the-future-of-software-engineering#user-content-fnref-1-7d7ee3" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
            <category>engineering</category>
            <category>ai</category>
        </item>
        <item>
            <title><![CDATA['Command not found' when running chruby]]></title>
            <link>https://jgulan.dev/blog/2024/02/27/chruby-error</link>
            <guid>https://jgulan.dev/blog/2024/02/27/chruby-error</guid>
            <pubDate>Tue, 27 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Why chruby may not be available after installation and how to load it correctly in an interactive shell.]]></description>
            <content:encoded><![CDATA[<p>Following the Ruby community’s advice to avoid using the system-installed Ruby instance for development, you
install <a href="https://github.com/postmodern/chruby" target="_blank" rel="noopener noreferrer" class="">chruby</a> using the <a href="https://github.com/postmodern/chruby?tab=readme-ov-file#install" target="_blank" rel="noopener noreferrer" class="">official installation instructions</a>
and intend to configure your Ruby versions using <a href="https://github.com/postmodern/ruby-install" target="_blank" rel="noopener noreferrer" class="">ruby-install</a>. After
installing chruby, you open a terminal and see the following error when running the command on your Unix-like OS
(Ubuntu Linux or MacOS):</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">$ chruby</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">chruby: command not found</span><br></div></code></pre></div></div>
<!-- -->
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-happened">What happened?<a href="https://jgulan.dev/blog/2024/02/27/chruby-error#what-happened" class="hash-link" aria-label="Direct link to What happened?" title="Direct link to What happened?" translate="no">​</a></h4>
<p>Let's take a closer look at what happened. The <a href="https://github.com/postmodern/chruby/blob/a543a35790e5528b5a67de20e78a7390f5f7606e/scripts/setup.sh#L65" target="_blank" rel="noopener noreferrer" class="">installation script</a> automatically adds a <em>/etc/profile.d</em> script at
<em>/etc/profile.d/chruby.sh</em>. The <em>/etc/profile.d</em> scripts are themselves auto-discovered and then executed by the
<em>/etc/profile</em> script, which one might expect to happen upon logging in:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">Extract from Ubuntu's /etc/profile file</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> /etc/profile.d </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token for-or-select variable" style="color:rgb(189, 147, 249);font-style:italic">i</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> /etc/profile.d/*.sh</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$i</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$i</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">unset</span><span class="token plain"> i</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><br></div></code></pre></div></div>
<p>However, <em>/etc/profile</em> is invoked only for login shells. A login shell is what we use when logging in via SSH or via a TTY.
Our terminal instance is an interactive non-login shell and so <em>/etc/profile</em> is not executed, including our new <em>chruby.sh</em> script that
would have made the chruby command available to our session.</p>
<p>To make the chruby command available to an interactive shell, we must configure our shell accordingly. For Bash this involves editing the
<em>~/.bashrc</em> or <em>/etc/bash.bashrc</em> files, which are the current user and global configuration scripts for Bash respectively:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">~/.bashrc</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">source</span><span class="token plain"> /usr/local/share/chruby/chruby.sh</span><br></div></code></pre></div></div>
<p>For ZSH on MacOS, the equivalent files would be <em>~/.zshrc</em> and <em>/etc/zshrc</em>. If using a shell other than Bash or ZSH, consult the documentation
to ensure the correct configuration script is updated.</p>
<p>Correctly configured, we now see the following when running chruby:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">$ chruby</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ruby-3.3.0</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-next">What next?<a href="https://jgulan.dev/blog/2024/02/27/chruby-error#what-next" class="hash-link" aria-label="Direct link to What next?" title="Direct link to What next?" translate="no">​</a></h4>
<p>Although the convention for Unix-like application installers is <em>not</em> to touch the user's data, the Google Cloud SDK installer asks users
whether it should update their <em>~/.bashrc</em> file as part of the installation process:</p>
<blockquote>
<p>Modify profile to update your $PATH and enable bash completion? (Y/n)?</p>
</blockquote>
<p>The chruby installer could help users by offering to do the same, even if only handling the most popular shells like Bash or ZSH.</p>]]></content:encoded>
            <category>engineering</category>
        </item>
        <item>
            <title><![CDATA[Image noise reduction using DxO PhotoLab]]></title>
            <link>https://jgulan.dev/blog/2023/01/25/image-noise-reduction-using-dxo-photolab</link>
            <guid>https://jgulan.dev/blog/2023/01/25/image-noise-reduction-using-dxo-photolab</guid>
            <pubDate>Wed, 25 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Using DxO PhotoLab's DeepPRIME algorithm to reduce image noise without sacrificing detail.]]></description>
            <content:encoded><![CDATA[<p><em>Disclaimer: I am a paying user of DxO PhotoLab, but have no other association or affiliation with the company. The opinions stated here are my own and I have not been incentivised to write them.</em></p>
<p>Some photographers enjoy the unobtrusive, film-like character that subtle image noise can add to an image, particularly for black-and-white photography.
In my experience, however, many of us prefer clean and noise-free images for the same reason that we prefer clean and noise-free audio — noise distracts our senses and clouds the signal.</p>
<!-- -->
<p>For images, noise can lead to the loss of detail and colour information. Luminance noise, which presents as random variations in pixel illumination, is easier to forgive, whereas chroma noise, which presents as random variations in pixel colour, is an unsightly distraction.</p>
<img align="left" class="with-margin" src="https://jgulan.dev/assets/images/noise-cd2fad532affc79089e75a83c71f0d45.jpg" alt="A noisy image">
<p>Recent advancements in image sensor technology mean that noise is less of an issue than it was say a decade ago, but image noise will still be noticeable when shooting at higher ISO values and can be particularly problematic for smaller image sensors that gather smaller quantities of light such as 1-inch, Micro Four Thirds and APS-C sensors.</p>
<p>Until recently, noise reduction algorithms have been fairly rudimentary, often resulting in a noticeable loss of detail as the image details are 'smoothed out' to hide the noise.
This changed with the release of <a href="https://www.dxo.com/dxo-photolab/" target="_blank" rel="noopener noreferrer" class="">DxO PhotoLab</a>'s DeepPRIME noise reduction algorithm. The exact inner workings of DxO's proprietary algorithm have not been disclosed by the company, however we know that
PhotoLab employs Artificial Intelligence in the form of a neural network that has been trained to perform the <a href="https://en.wikipedia.org/wiki/Demosaicing" target="_blank" rel="noopener noreferrer" class="">demosaicing</a> and denoising processes.</p>
<p>In any case, the results speak for themselves. Below is a 100% crop of a raw image taken of a Christmas elf figure in low-light conditions at ISO 1250 using my Olympus OM-D E-M1 Mark II, paired with the Olympus 25mm f1.2 PRO lens.
If it is not already clear, the left side is the image processed without noise reduction and the right side is the image processed with PhotoLab's DeepPRIME noise reduction (v5.5).</p>
<div class="with-margin" style="position:relative;overflow:hidden;user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none" data-rcs="root"><img src="https://jgulan.dev/assets/images/denoised-100pc-6b199029b848a9f3909c38ac847f90d7.jpg" alt="After noise reduction (100% crop)" style="display:block;width:100%;height:100%;max-width:100%;box-sizing:border-box;object-fit:cover;object-position:center" data-rcs="image"><div style="position:absolute;top:0;left:0;width:100%;height:100%;will-change:clip;user-select:none;-khtml-user-select:none;-moz-user-select:none;-webkit-user-select:none" data-rcs="clip-item"><img src="https://jgulan.dev/assets/images/noise-100pc-c2c819e6a319f0c29e9d0a9259b865e8.jpg" alt="Before noise reduction (100% crop)" style="display:block;width:100%;height:100%;max-width:100%;box-sizing:border-box;object-fit:cover;object-position:center" data-rcs="image"></div><div style="position:absolute;top:0;width:100%;height:100%;pointer-events:none" data-rcs="handle-container"><div style="position:absolute;height:100%;transform:translateX(-50%);pointer-events:all"><div class="__rcs-handle-root" style="display:flex;flex-direction:column;place-items:center;height:100%;cursor:ew-resize;pointer-events:none;color:#fff"><div class="__rcs-handle-line" style="flex-grow:1;height:100%;width:2px;background-color:currentColor;pointer-events:auto;box-shadow:0 0 7px rgba(0,0,0,.35)"></div><div class="__rcs-handle-button" style="display:grid;grid-auto-flow:column;gap:8px;place-content:center;flex-shrink:0;width:56px;height:56px;border-radius:50%;border-style:solid;border-width:2px;pointer-events:auto;backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);box-shadow:0 0 7px rgba(0,0,0,.35)"><div style="width:0;height:0;border-top:8px solid transparent;border-right:10px solid;border-bottom:8px solid transparent"></div><div style="width:0;height:0;border-top:8px solid transparent;border-right:10px solid;border-bottom:8px solid transparent;transform:rotate(180deg)"></div></div><div class="__rcs-handle-line" style="flex-grow:1;height:100%;width:2px;background-color:currentColor;pointer-events:auto;box-shadow:0 0 7px rgba(0,0,0,.35)"></div></div></div></div></div>
<p>A further enlargement of the same crop follows:</p>
<div class="with-margin" style="position:relative;overflow:hidden;user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none" data-rcs="root"><img src="https://jgulan.dev/assets/images/denoised-200pc-0f2e930ad280dc9b0f382d21150a07b7.jpg" alt="After noise reduction (200% crop)" style="display:block;width:100%;height:100%;max-width:100%;box-sizing:border-box;object-fit:cover;object-position:center" data-rcs="image"><div style="position:absolute;top:0;left:0;width:100%;height:100%;will-change:clip;user-select:none;-khtml-user-select:none;-moz-user-select:none;-webkit-user-select:none" data-rcs="clip-item"><img src="https://jgulan.dev/assets/images/noise-200pc-a2a7ad0d2941b3c978235b72ceb5a6bf.jpg" alt="Before noise reduction (200% crop)" style="display:block;width:100%;height:100%;max-width:100%;box-sizing:border-box;object-fit:cover;object-position:center" data-rcs="image"></div><div style="position:absolute;top:0;width:100%;height:100%;pointer-events:none" data-rcs="handle-container"><div style="position:absolute;height:100%;transform:translateX(-50%);pointer-events:all"><div class="__rcs-handle-root" style="display:flex;flex-direction:column;place-items:center;height:100%;cursor:ew-resize;pointer-events:none;color:#fff"><div class="__rcs-handle-line" style="flex-grow:1;height:100%;width:2px;background-color:currentColor;pointer-events:auto;box-shadow:0 0 7px rgba(0,0,0,.35)"></div><div class="__rcs-handle-button" style="display:grid;grid-auto-flow:column;gap:8px;place-content:center;flex-shrink:0;width:56px;height:56px;border-radius:50%;border-style:solid;border-width:2px;pointer-events:auto;backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);box-shadow:0 0 7px rgba(0,0,0,.35)"><div style="width:0;height:0;border-top:8px solid transparent;border-right:10px solid;border-bottom:8px solid transparent"></div><div style="width:0;height:0;border-top:8px solid transparent;border-right:10px solid;border-bottom:8px solid transparent;transform:rotate(180deg)"></div></div><div class="__rcs-handle-line" style="flex-grow:1;height:100%;width:2px;background-color:currentColor;pointer-events:auto;box-shadow:0 0 7px rgba(0,0,0,.35)"></div></div></div></div></div>
<p>The reduction in noise is significant, all without any obvious loss of image detail. DeepPRIME noise reduction has been a game-changer for my Micro Four Thirds photography and I look forward to seeing what further enhancements
the team at DxO Labs can make.</p>]]></content:encoded>
            <category>photography</category>
        </item>
        <item>
            <title><![CDATA[Detecting regression commits using git bisect]]></title>
            <link>https://jgulan.dev/blog/2023/01/15/detecting-regression-commits-using-git-bisect</link>
            <guid>https://jgulan.dev/blog/2023/01/15/detecting-regression-commits-using-git-bisect</guid>
            <pubDate>Sun, 15 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Find the commit that introduced a regression efficiently using Git's built-in binary search tool.]]></description>
            <content:encoded><![CDATA[<p>Locating the commit that introduced a regression, or some undesirable change, in a codebase can be difficult. Locating the same in a frequently-changing codebase with a large number of contributors is harder again.</p>
<p>Fortunately <a href="https://git-scm.com/docs/git-bisect" target="_blank" rel="noopener noreferrer" class=""><code>git bisect</code></a> provides a helpful tool for doing precisely this. Rather than performing a linear search of the commits, <code>git bisect</code> uses a clever binary search algorithm to locate the offending commit far more efficiently.</p>
<!-- -->
<div class="center with-margin"><svg xmlns="http://www.w3.org/2000/svg" width="593.29" height="260.27" viewBox="0 0 156.97 68.863" class="bisect_xRd9"><title>Git Bisect Visualisation</title><defs><marker id="TriangleStart" markerHeight="6.155" markerWidth="5.324" orient="auto-start-reverse" overflow="visible" preserveAspectRatio="xMidYMid" viewBox="0 0 5.324 6.155"><path fill="context-stroke" fill-rule="evenodd" stroke="context-stroke" stroke-width=".5pt" d="M2.885 0-1.44 2.5v-5z"></path></marker><style>.guide{fill:none;stroke:var(--ifm-color-emphasis-500, #7b8491);stroke-linecap:round;stroke-linejoin:round}.commits rect,.status rect{rx:1.2px}.status rect{stroke:color-mix(in srgb,currentColor 35%,transparent);stroke-width:.28px}.result-label tspan,.status text,.status tspan{fill:#fff;font-family:ui-sans-serif,system-ui,sans-serif;font-weight:650;letter-spacing:.08px}.good rect{fill:#2f9e72}.bad rect{fill:#d95d58}.commits rect{fill:#3b4048;stroke:var(--ifm-color-emphasis-500, #7b8491);stroke-width:.28px}.commits text,.commits tspan{fill:#f4f1ec;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-weight:650}</style></defs><path d="M18.004.533h156.84v68.612H18.004z" style="fill:transparent" transform="translate(-18.006 -.533)"></path><g fill="#1a1a1a" stroke="#323232" class="guide"><path stroke-dasharray="1.76709, 0.441771" stroke-width="0.444" d="M79.658 57.627V25.93"></path><path stroke-dasharray="1.68087, 0.420216" stroke-width="0.422" d="m51.04 57.627-.115-19.754"></path><path stroke-width="0.965" d="M22.943 57.376V9.502M133.764 57.375V9.5"></path></g><g stroke="#323232" class="guide"><path stroke="#bdc2c8" stroke-width="0.507" marker-end="url(#TriangleStart)" d="M97.649 24.312v1.522l-29.212.031.08 5.505" transform="translate(-18.006 -.533)"></path><path stroke-width="0.507" marker-end="url(#TriangleStart)" d="m68.518 33.251-.08 4.492 14.218-.08v18.324" transform="translate(-18.006 -.533)"></path><path d="M72.897 41.707h18.875v10.568H72.897z" style="rx:1.2px;fill:var(--editorial-brand-accent, #ca652c);stroke:color-mix(in srgb,currentColor 35%,transparent);stroke-width:.28px;filter:drop-shadow(0 .7px 1px rgb(0 0 0/22%))" transform="translate(-18.006 -.533)"></path></g><g class="status good"><path fill="#2f9e72" stroke="#323232" stroke-width="0.166" d="M16.257 2.004h13.286v7.389H16.257z"></path><text xml:space="preserve" x="40.939" y="7.494" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="40.939" y="7.494" fill="#2e2e2e" font-family="'Segoe UI'">Good</tspan></text></g><g class="status bad"><path fill="#d95d58" stroke="#323232" stroke-width="0.166" d="M81.906 17.697h13.286v7.389H81.906z"></path><text xml:space="preserve" x="106.566" y="22.995" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="106.566" y="22.995" fill="#2e2e2e" font-family="'Segoe UI'">Bad</tspan></text></g><g class="status bad"><path fill="#d95d58" stroke="#323232" stroke-width="0.166" d="M127.104 2.02h13.286v7.39h-13.286z"></path><text xml:space="preserve" x="151.748" y="7.491" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="151.748" y="7.491" fill="#2e2e2e" font-family="'Segoe UI'">Bad</tspan></text></g><text xml:space="preserve" x="82.317" y="46.028" stroke-width="0.507" class="result-label" font-size="3.528" style="fill:#fff;font-family:ui-sans-serif,system-ui,sans-serif;font-weight:650;letter-spacing:.08px" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="82.317" y="46.028"><tspan x="82.317" y="46.028" font-family="'Segoe UI'">First bad</tspan></tspan><tspan x="82.317" y="50.437" font-family="'Segoe UI'">commit</tspan></text><g class="status good"><path fill="#2f9e72" stroke="#323232" stroke-width="0.166" d="M35.048 27.114h13.286v7.389H35.048z"></path><text xml:space="preserve" x="59.731" y="32.605" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="59.731" y="32.605" fill="#2e2e2e" font-family="'Segoe UI'">Good</tspan></text></g><g class="commits"><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M1.907 57.25h13.661v9.582H1.907z"></path><text xml:space="preserve" x="26.875" y="63.842" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="26.875" y="63.842" font-family="'Segoe UI'">1</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M15.834 57.25h13.661v9.582H15.834z"></path><text xml:space="preserve" x="40.71" y="63.842" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="40.71" y="63.842" font-family="'Segoe UI'">2</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M29.75 57.25h13.661v9.582H29.75z"></path><text xml:space="preserve" x="54.593" y="63.82" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="54.593" y="63.82" font-family="'Segoe UI'">3</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M43.68 57.25h13.661v9.582H43.68z"></path><text xml:space="preserve" x="68.579" y="63.837" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="68.579" y="63.837" font-family="'Segoe UI'">4</tspan></text><g class="first-bad-commit"><path d="M75.613 57.784h13.661v9.581H75.613z" style="fill:color-mix(in srgb,var(--editorial-brand-accent, #ca652c) 28%,#3b4048);stroke:var(--editorial-brand-accent, #ca652c);stroke-width:.55px" transform="translate(-18.006 -.533)"></path><text xml:space="preserve" x="82.441" y="63.798" stroke-width="0.507" font-family="Arial" font-size="3.528" style="fill:var(--editorial-brand-accent, #ca652c)" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="82.441" y="63.798" font-family="'Segoe UI'" style="fill:var(--editorial-brand-accent, #ca652c)">5</tspan></text></g><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M71.524 57.25h13.661v9.582H71.524z"></path><text xml:space="preserve" x="96.375" y="63.821" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="96.375" y="63.821" font-family="'Segoe UI'">6</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M85.454 57.25h13.661v9.582H85.454z"></path><text xml:space="preserve" x="110.286" y="63.821" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="110.286" y="63.821" font-family="'Segoe UI'">7</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M99.384 57.25h13.661v9.582H99.384z"></path><text xml:space="preserve" x="124.223" y="63.821" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="124.223" y="63.821" font-family="'Segoe UI'">8</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M113.294 57.25h13.661v9.582h-13.661z"></path><text xml:space="preserve" x="138.138" y="63.821" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="138.138" y="63.821" font-family="'Segoe UI'">9</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M127.224 57.25h13.661v9.582h-13.661z"></path><text xml:space="preserve" x="151.955" y="63.821" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="151.955" y="63.821" font-family="'Segoe UI'">10</tspan></text><path fill="#1f1f1f" stroke="#323232" stroke-width="0.265" d="M141.154 57.25h13.661v9.582h-13.661z"></path><text xml:space="preserve" x="166.122" y="63.842" fill="#2e2e2e" stroke-width="0.507" font-family="Arial" font-size="3.528" text-anchor="middle" transform="translate(-18.006 -.533)"><tspan x="166.122" y="63.842" font-family="'Segoe UI'">11</tspan></text></g><path fill="none" stroke="#323232" stroke-width="0.457" marker-start="url(#TriangleStart)" d="M97.661 22.474V12.338" class="guide" transform="translate(-18.006 -.533)"></path></svg></div>
<p>Having identified a commit from the past where the regression did not exist (e.g. <code>git checkout</code> a commit from say a month ago), my typical usage of <code>git bisect</code> is along the lines of:</p>
<ol>
<li class="">Check out the branch containing the regression and run <code>git bisect start</code>.</li>
<li class="">Label the good and bad commits: <code>git bisect good &lt;commit hash&gt;</code> and <code>git bisect bad &lt;commit hash&gt;</code>. The current commit is taken if the commit hash argument is omitted.</li>
<li class="">Bisecting commences — a candidate commit is automatically checked out and the number of revisions left to test and number of remaining steps are printed.  You can run <code>git bisect reset</code> to abort at any stage.</li>
<li class="">Check whether the regression still exists, whether this involves running an automated test or taking manual replication steps. If the candidate commit contains the regression, run <code>git bisect bad</code>, otherwise run <code>git bisect good</code>. If any search area remains, another candidate commit is automatically checked out.</li>
<li class="">Repeat #4 until the bisecting process concludes and the bad commit's hash is listed.</li>
</ol>
<p>Given <code>git bisect</code>'s binary search technique, the number of steps required will only increase logarithmically as the number of commits increases — this is a time-saving tool that I turn to again and again.</p>]]></content:encoded>
            <category>engineering</category>
        </item>
    </channel>
</rss>