<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.gustavwengel.dk/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.gustavwengel.dk/" rel="alternate" type="text/html" /><updated>2026-07-21T08:47:02+00:00</updated><id>https://www.gustavwengel.dk/feed.xml</id><title type="html">Tinkerer</title><subtitle>Code and Climate Change. Blog about software development in ClimateTech</subtitle><author><name>Gustav Wengel</name></author><entry><title type="html">Carbon Accounting for Software Developers</title><link href="https://www.gustavwengel.dk/carbon-accounting-for-software-developers" rel="alternate" type="text/html" title="Carbon Accounting for Software Developers" /><published>2026-07-20T00:00:00+00:00</published><updated>2026-07-20T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/carbon-accounting-for-developers</id><content type="html" xml:base="https://www.gustavwengel.dk/carbon-accounting-for-software-developers"><![CDATA[<p>I’ve previously written about that I think having <a href="/value-of-domain-knowledge">domain knowledge</a> as a software developer is tremendously important, and really helps you be effective in your role.<br />
As I currently work with carbon emissions and carbon accounting at <a href="https://www.climatiq.io/">Climatiq</a>, I figured I’d put together a little reading list of what to read in this space if you’re a software developer / software engineer looking to get a little more domain knowledge.</p>

<h1 id="carbon-accounting">Carbon Accounting</h1>

<p>For corporate carbon accounting, the standard to read is the <a href="https://ghgprotocol.org/corporate-standard">Greenhouse Gas Protocol Corporate Standard</a>. This standard explains the different reasons behind doing corporate carbon accounting, and sets out guidelines and requirements for doing so. I would recommend reading (or at least skimming) the entire thing in full. This is <em>the</em> text to read when understanding the carbon accounting space.</p>

<p>Greenhouse Gas Protocol (GHGP) also publishes <a href="https://ghgprotocol.org/standards-guidance">other standards &amp; guidance documents</a>, like guidance for calculating Scope 2 and 3. I have not read all of these myself, but I’d consider them relevant to skim if you’re doing work that deals with one of those areas in particular. In particular I think the <a href="https://ghgprotocol.org/corporate-value-chain-scope-3-standard">Corporate Value Chain (Scope 3)</a> standard is worth skimming.</p>

<p>Here’s also a few notes of things I thought were particularly interesting or important regarding corporate carbon accounting.</p>

<h2 id="general">General</h2>
<ul>
  <li>The GHGP starts with a section about why businesses should do carbon accounting, and what outcomes they might be interested in. In my mind, of course companies should care about sustainability, but I think it’s important to remember that in many cases sustainability teams have to fight for budget and demonstrate their work is worth doing.</li>
  <li>In general, carbon accounting is significantly more.. imprecise than I originally thought. There’s <em>a lot</em> of assumptions that goes into carbon accounting, simply because not making a lot of assumptions is going to be very expensive.
    <ul>
      <li>This is also why you cannot compare the carbon accounting of companies. The GHGP is pretty clear that carbon accounting for a company, is to track reductions <em>for that company only</em>, and should not be compared with other companies.<sup id="fnref:0" role="doc-noteref"><a href="#fn:0" class="footnote" rel="footnote">1</a></sup></li>
      <li>In carbon accounting, the concept of “baseline year” exists, which is the year when you start your carbon accounting baseline from. You then compare yourself with your baselines year to track reductions.</li>
      <li>Surprisingly, the CO2e number from the base year is not set in stone. If you e.g. do a corporate re-structure, or change calculation methodology, you have to do “rebaselining”, which is essentially re-calculating the emissions from your baseline year so they’re still comparable with the current year. This makes sense, but it’s fairly unintuitive if you don’t know about it.</li>
    </ul>
  </li>
</ul>

<hr />

<ul>
  <li>There’s a distinction between primary data and secondary data. Secondary data is generally emission factors compiled from industry averages, where primary data is when <em>your</em> supplier says how much emissions a product emitted during production.
    <ul>
      <li>Depending on your supplier, they might be calculating that information primarily from secondary data themselves (hopefully enriched with <em>some</em> data like their own fuel and electricity usage). This means that results calculated primarily from secondary data can <em>become</em> primary data, which seems a bit odd.</li>
    </ul>
  </li>
</ul>

<hr />

<ul>
  <li>Generally, it seems like carbon accounting is a journey. You often tend to go wide to get an overview of where your emissions are, often with something like spend-based data first, and then you identify hotspots where it’s worth to get more granular data.
    <ul>
      <li>Spend-based data is where you know how much money you spent on e.g. electronics, and then you multiply that with an extremely rough emission factor, to figure out your emissions. This is extremely inaccurate, but good to get a first picture of where it’s worth looking deeper.</li>
    </ul>
  </li>
  <li>Then, when you know where to dive deeper, you have to get more data, e.g. litres of fuel consumed, kg of product bought, etc. This is generally talked about as activity-based data, and considered higher accuracy, but also more time-consuming to calculate and acquire the data for.</li>
  <li>In later stages of the journey you might start swapping secondary out for primary data, if you can get your suppliers to give you the primary data. This is often something people struggle with getting out of their suppliers.</li>
</ul>

<h2 id="scopes">Scopes</h2>
<ul>
  <li>Corporate Carbon Accounting it split up into three scopes.
    <ul>
      <li>Scope 1 (the fuel you burn in stuff you own, or emissions from chemical processes under your control)</li>
      <li>Scope 2 (emissions from the electricity you purchase)</li>
      <li>Scope 3 (everything else really).</li>
    </ul>
  </li>
  <li>Under the GHGP Scope 1 and 2 reporting is mandatory, while Scope 3 is optional.</li>
  <li>The scopes seem primarily designed to avoid double-counting, which is when multiple companies count the same carbon emissions. Scope 1 and 2 are designed so that they cannot be double-counted, while your scope 3 is by nature someone else’s scope 1 &amp; 2.</li>
  <li>
    <p>Double counting is defined as:<br />
<em>“Double counting or double claiming occurs when two or more companies claim ownership for a single GHG reduction within the same scope.”</em><br />
It makes sense that it’s important to avoid double counting if you e.g. do trading with carbon credits, but if you’re “just” disclosing it probably doesn’t matter all that much.</p>
  </li>
  <li>Scopes are primarily designed based on company structure - there’s a lot of rules about how you determine if something counts as your scope 1 &amp; 2, especially if your company owns (parts of) other companies.</li>
  <li>Scopes seem very game-able if you want to make your carbon accounting look good. If all your scope 1 comes from burning gasoline in cars your company owns, you can just sell the cars and lease identical cars. <br />
Now the gasoline suddenly counts as scope 3 emissions instead. And you don’t <em>have</em> to report on those, so your carbon accounting disclosure looks great, with no change in actual emissions anywhere.</li>
</ul>

<h2 id="allocations">Allocations</h2>

<ul>
  <li>It seems to me like Carbon Accounting is kind of like trying to model the physical world. You want to know how much stuff you emit in the end, and you might need to trace the path of your products all the way down across multiple companies, to figure out the final emissions.</li>
  <li>This becomes particularly tricky if you need to do <em>allocation</em> at one point in the journey.</li>
  <li>Allocation means, that if you have a system that produces multiple things in the same process, you’ll need to figure out how to allocate the shared emissions to each part.</li>
  <li>An example: You have a factory that various items. You only have a power meter for the entire factory. This means you only know the overall emissions for the whole factory, which you’ll need to allocate out across the products.
    <ul>
      <li>The best thing to do, is do no allocation, which means installing e.g. electricity meters on each manufacturing line if possible.</li>
    </ul>
  </li>
  <li>Often allocation cannot be solved by e.g. adding more monitoring:</li>
  <li>If you raise and slaughter a dairy cow you get many products. Milk during it’s lifetime, and meat and leather after the slaughter. Cows emit carbon from food and methane from the digestion. Should those emissions count towards the milk? The leather? Everything equally?
    <ul>
      <li>In cases like that you’ll need to do explicit allocation. The GHGP has some different strategies and a “hierarchy” for when different kinds are good to use, but in the end it becomes a question of judgment.</li>
    </ul>
  </li>
</ul>

<h1 id="electricity">Electricity</h1>
<ul>
  <li>Accounting for electricity when doing scope 2 has more nuances than you might immediately expect.</li>
  <li>You do two types of accounting and report on both of them; location-based and market-based.
    <ul>
      <li>Location based is the actual emissions of the grid energy you’re using. The emissions will vary depending on what the grid makeup is, the percentage of renewables etc.</li>
      <li>Then there’s market-based, which exists because you can sell renewable energy certificates. So someone can buy renewable energy certificates to get a certification that their electricity is 100% renewable.<br />
But in practice the grid mix is what it is, so the certificates essentially means <em>someone</em> gets the green energy and you get to take credit for it.<br />
The market based approach takes this into account, so if you don’t have any energy certificates, you’re supposed to calculate using the “residual mix”, which is the emission factor of the energy after all the green energy with certificates have been taken out of it. These generally come with fairly high emissions, as most/all of the renewables have been taken out.</li>
    </ul>
  </li>
  <li>Even though you have to account for electricity in scope 2, this is only part of the picture. There’s parts of the electricity that’s not covered, such as:
    <ul>
      <li>The transmission &amp; distribution losses when transporting the electricity. These hover between 4 and 20% <a href="https://data.worldbank.org/indicator/EG.ELC.LOSS.ZS">depending on the country</a>.</li>
      <li>The extraction of the fuel. The fuel extraction might particularly be relevant for fuels like natural gas, where methane leaks make <a href="https://www.nature.com/articles/s41467-023-41527-9">up more than 30% of the direct CO2e emissions</a>, which wouldn’t go into your Scope 2, which makes Natural gas seem significantly more climate-friendly than it is.</li>
    </ul>
  </li>
  <li>These additional emissions from electricity consumption go into your scope 3.</li>
</ul>

<h1 id="product-carbon-footprints">Product Carbon Footprints</h1>

<p>Corporate Carbon Accounting is one thing, that works at a corporate level. Another area is Product Carbon Footprints (PCF) which tries to calculate the carbon emissions of a single product.</p>

<p>Product Carbon Footprint is governed by multiple different standards that agree on <em>most</em> things but not all things, which is a royal pain in the ass.</p>

<ul>
  <li>The <a href="https://ghgprotocol.org/product-standard">Greenhouse Gas Protocol Standard</a>, I think is worth reading. It has the advantages of being commonly used, and very easy to read.</li>
  <li>ISO 14067 which is built on top of ISO 14040 and ISO 14044. I don’t think these standards are worth reading, they’re hairy, you have to purchase them, and they’re hard to read. Unless you really know you need these, skip ‘em.
    <ul>
      <li>Fun side-note: The ISO standards call it “The Carbon Footprint of a Product (CFP)” but the rest of the world seems to call it a “Product Carbon Footprint”.</li>
    </ul>
  </li>
  <li>The <a href="https://www.carbon-transparency.org/">PACT Methodology</a>, which is really more of a project that aims to enable interoperability and easy sharing of PCFs<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup>, but comes with methodological guidance as well. This isn’t as widely used and known as the other two standards, but it has the advantage of being pragmatic and very well-written, which means it’s a great introduction to the concept of a PCF, and how those calculations work.</li>
</ul>

<p>As before, here are a few of my personal notes:</p>

<ul>
  <li>The standards don’t agree on everything. ISO and GHGP agrees on most things, but e.g. PACT has very different ideas of how data quality works (data quality is qualitative in the other standards, but quantitative in PACT)</li>
  <li>PCFs come in two variants. Cradle-to-gate, which is the production of something, and cradle-to-grave, which also encompasses the use of the product, and the disposal. The disposal generally doesn’t contribute hugely to emissions, but the use-phase might, particularly for products that use electricity.
    <ul>
      <li>In some cases a use-phase might also be something you don’t expect. E.g. you can include the energy costs to wash a piece of clothing in the use-phase of that clothing. This might make sense if you e.g. are creating odor-resistant clothing that needs less washing.</li>
    </ul>
  </li>
  <li>Product Carbon Footprints are often not created based on an end-product - they’re calculated based on a “Functional unit”, which is what <em>function</em> the product serves. Examples of functional units:
    <ul>
      <li>“Washing 5kg of laundry every week for 10 years” ( A laundry machine )</li>
      <li>“Provide covering for the torso for five years” ( A t-shirt )</li>
      <li>“Provide color and environmental protection for five years for 50m2” ( A liter of paint )</li>
    </ul>
  </li>
  <li>The functional units might seem silly at first, but they make sense, as they allow for capturing e.g. upgrades to product durability, and can help compare products, so you don’t accidentally end up comparing paint that lasts 1 year with paint that lasts 15 years.</li>
  <li>Product Carbon Footprints, frustratingly enough, are also not comparable. This is the same as Corporate Carbon Accounting, even though intuitively you’d expect two PCFs for different kinds of screws to be comparable, they’re not.
    <ul>
      <li>For PCFs to be comparable, they need to make the same assumptions. For some sectors there exists “Sector-specific guidelines”, which is the list of agreed upon assumptions to make. Two PCFs calculated under the same sector-specific guidelines <em>are</em> comparable.</li>
      <li>For PCFs to be comparable they also need the same functional unit. That way you don’t naively compare a liter of paint that covers 40m2 with one that covers 60m2.</li>
    </ul>
  </li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0" role="doc-endnote">
      <p>Quote from the Greenhouse gas protocol: Use of this standard is intended to enable comparisons of a company’s GHG emissions over time. It is not designed to support comparisons between companies based on their scope 3 emissions. Differences in reported emissions may be a result of differences in inventory methodology or differences in company size or structure. Additional measures are necessary to enable valid comparisons across companies. Such measures include consistency in methodology and data used to calculate the inventory, and reporting of additional information such as intensity ratios or performance metrics. Additional consistency can be provided through GHG reporting programs or sector- specific guidance <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>Remember when I said that carbon accounting was like modeling the world? Turns out that’s a lot harder if you have to do it through sending PDFs in emails. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[I’ve previously written about that I think having domain knowledge as a software developer is tremendously important, and really helps you be effective in your role. As I currently work with carbon emissions and carbon accounting at Climatiq, I figured I’d put together a little reading list of what to read in this space if you’re a software developer / software engineer looking to get a little more domain knowledge.]]></summary></entry><entry><title type="html">The Value of Domain Knowledge in Software</title><link href="https://www.gustavwengel.dk/value-of-domain-knowledge" rel="alternate" type="text/html" title="The Value of Domain Knowledge in Software" /><published>2026-07-19T00:00:00+00:00</published><updated>2026-07-19T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/value-of-domain-knowledge</id><content type="html" xml:base="https://www.gustavwengel.dk/value-of-domain-knowledge"><![CDATA[<p>Something that I think is often overlooked by many software developers is the value of <a href="https://en.wikipedia.org/wiki/Domain_knowledge">domain knowledge</a>, i.e. understanding the industry you’re in and the mindset of the end-users of your software<sup id="fnref:0" role="doc-noteref"><a href="#fn:0" class="footnote" rel="footnote">1</a></sup>.</p>

<p>I actually think that explicitly spending time to acquire domain knowledge might be one of the <strong>best</strong> uses of your time as a software developer for a few reasons:</p>

<ul>
  <li>
    <p>It allows you to act with significantly more autonomy. If you lack domain knowledge, it’s hard to evaluate whether user-facing decisions are a good idea, so you’ll have to get feedback from someone who does know.<br />
If you live in a fairytale where all tasks are perfectly specified up front with no room for ambiguity this might not be a problem you have. For the rest of us, having domain knowledge allows skipping a lot of back and forth because you often already have the knowledge you need to make decisions.</p>
  </li>
  <li>
    <p>Domain knowledge is great when scoping new projects or features. Having both domain knowledge so you know what to build <em>and</em> the technical experience to understand how to fit it into the existing product is a good skillset to have when figuring out what an MVP looks like.</p>
  </li>
  <li>
    <p>It’s just plain fun. Getting to learn about a new industry and domain is one of the really cool things about building software in my opinion.</p>
  </li>
</ul>

<hr />

<h2 id="types-of-knowledge">Types of Knowledge</h2>

<p>In my mind, you can split the knowledge required to be effective at building software into four (extremely coarse) categories:</p>

<h4 id="foundational-knowledge">Foundational Knowledge</h4>
<p>The foundational knowledge about how computers and their building blocks work. This could be about how processors work, about complexities of algorithms, client/server infrastructure, when and how to cache, etc.</p>

<p>This kind of knowledge is often something that’s picked up via formal education, but can also be picked up via reading books, or sometimes by learning painful lessons on the job.<br />
This kind of knowledge generally carries over across jobs, and is useful in almost all situations.</p>

<h4 id="stack-specific-knowledge">Stack-specific Knowledge</h4>
<p>Then there’s knowledge that’s specific to your stack. How to make the best use of your programming language, what weird quirks or footguns it has<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup> or how to best make use of the framework on top of your programming language.<br />
This knowledge generally carries over across jobs if you’re using the same stack, but if you’re starting over on a completely new stack you’ll have to re-learn new stack-specific knowledge.</p>

<h4 id="product-knowledge">Product Knowledge</h4>
<p>Next up is the knowledge about the product or service you’re working on <em>right this moment</em>. This is generally always going to be job-specific and it means that when you switch jobs you’ll always have to re-learn this.<br />
Depending on how many products your company has, you might even have to learn this multiple times in the same job.</p>

<h4 id="domain-knowledge">Domain Knowledge</h4>
<p>Last is the knowledge about the domain your end-users are operating in. Personally I think domain knowledge is so broad that it’s hard to define, but here’s some examples:</p>

<ul>
  <li>When working with wind turbine software it turned out to be really handy to know a lot about how wind turbines worked even, if I never had to interact with one physically.<br />
I read the <a href="https://onlinelibrary.wiley.com/doi/book/10.1002/9781119992714">Wind Energy Handbook</a> and actually ended up teaching a “Wind Turbine for Software Developers” course to other companies in the industry.</li>
  <li>When working with carbon emissions estimations at <a href="https://www.climatiq.io/">Climatiq</a> reading the relevant standards, like the <a href="https://ghgprotocol.org/corporate-standard">Greenhouse Gas Protocol Corporate Standard</a> about how carbon accounting works, helped me make lots of decisions much quicker than if I had needed someone to explain the relevant context to me each time.</li>
  <li>When I was doing a startup in the waste management industry we interviewed waste haulers, and actually also got to do a full work-day with one of them, to see what their day-to-day was like.</li>
</ul>

<p>As you can see, domain knowledge can take many forms. The three example above cover physical/mechanical constraints, industry standards, and end-user behaviour / mindset.</p>

<p>In the end we’re trying to get enough big-picture knowledge, that we can easily put ourselves in our users shoes and therefore make good decisions that takes them into account.</p>

<h2 id="how-to-learn-domain-knowledge">How to learn domain knowledge</h2>

<p>I think that we acquire the different types of knowledge differently.</p>

<p>I think it’s fair to say that most of the product knowledge and stack knowledge comes from the day-to-day work.<br />
When starting on a new stack or product, you might do some tutorials and read some guides, but afterward the learning comes naturally as part of the regular workflow.</p>

<p>On the opposite end of the spectrum, I think that both foundational knowledge and domain knowledge requires explicit “learning time”.</p>

<p>This is because they often require stepping back from the task that’s right ahead of us to look at the bigger picture.</p>

<p>While foundational knowledge is often something that you’ve been taught as part of an education, domain knowledge isn’t, as it’s well.. domain-specific.</p>

<p>Some ways to acquire domain knowledge is:</p>
<ul>
  <li>Reading introductory textbooks in your domain</li>
  <li>Attending introductory courses in your domain</li>
  <li>Talking to end-users that are using your product.</li>
</ul>

<p>I think many of us might feel we’re too busy to take a step back and schedule time for getting the domain knowledge we need.<br />
But we should take that time. It pays off in the long run.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0" role="doc-endnote">
      <p>Depending on how large systems you’re working with, you might have users of your software module, and those might have end-users. The distinction can get fairly blurry in large-enough systems, but let’s consider understanding the final end-users along with any direct users, as part of relevant domain knowledge. <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments">Python mutable default arguments</a>, I’m looking at you. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[Something that I think is often overlooked by many software developers is the value of domain knowledge, i.e. understanding the industry you’re in and the mindset of the end-users of your software1. Depending on how large systems you’re working with, you might have users of your software module, and those might have end-users. The distinction can get fairly blurry in large-enough systems, but let’s consider understanding the final end-users along with any direct users, as part of relevant domain knowledge. &#8617;]]></summary></entry><entry><title type="html">Website AI Policy</title><link href="https://www.gustavwengel.dk/website-ai-policy" rel="alternate" type="text/html" title="Website AI Policy" /><published>2026-05-20T00:00:00+00:00</published><updated>2026-05-20T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/website-ai-policy</id><content type="html" xml:base="https://www.gustavwengel.dk/website-ai-policy"><![CDATA[<p>Have you noticed that it’s gotten harder to trust things on the internet nowadays?</p>

<p>I used to be able to fairly quickly determine whether an article answered my question, and if the author seemed competent enough to warrant reading.</p>

<p>However, with LLM’s getting better at disguising themselves, I now often spend several minutes figuring out whether an article is worth reading or not (if I wanted to ask an LLM, I would have just done that.)</p>

<p>To avoid people reading this site having to face the same uncertainty, I wanted to clarify the policy on LLM-generated content on this website. It’s none of it. Every word is human-authored by me, and all mistakes and bad takes are mine.</p>

<p>I might use LLMs to do research, and spar with, but I will fact-check, and the final output is authored by my hand alone (and sometimes with corrections from kind people who submit a Pull Request).</p>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[Have you noticed that it’s gotten harder to trust things on the internet nowadays?]]></summary></entry><entry><title type="html">Moving My Digital Footprint out of the United States - Final Part</title><link href="https://www.gustavwengel.dk/2026/02/21/out-of-us-4.html" rel="alternate" type="text/html" title="Moving My Digital Footprint out of the United States - Final Part" /><published>2026-02-21T00:00:00+00:00</published><updated>2026-02-21T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/2026/02/21/out-of-us-4</id><content type="html" xml:base="https://www.gustavwengel.dk/2026/02/21/out-of-us-4.html"><![CDATA[<p>I’m trying to rely less on US services &amp; tech as mentioned in the <a href="/2026/02/01/out-of-us-1.html">first part of this series</a>, and by this point I’m as done as I want to be.</p>

<p>The last thing I had left to migrate, was my side project <a href="https://grønpension.nu/">grønpension.nu</a> (it’s in danish!) which is a site I help host, to allow users to easily submit a Power of Attorney to their pension fund, to vote to divest from fossil fuel companies.<br />
This was the last service where I actively paid money to US companies every month.</p>

<p>I moved it from DigitalOcean to <a href="https://www.scaleway.com">Scaleway</a> which is an European cloud that I’ve worked with before, and that I’ve generally been fairly happy with.</p>

<p>The switchover was smooth-ish. I think it could have gone smoother if I had done a straight 1:1 transfer, but as the website is fairly seasonally active, I wanted to switch from an always-on to a serverless setup.</p>

<p>It took a few hours to figure out how to actually deploy it from Github on pushes to main - there are some Github Actions for Scaleway but they’re not necessarily super well maintained. I think that’s probably the cost of moving to an ecosystem that’s not quite as popular, and with a smaller focus on developer experience for small projects than DigitalOcean.</p>

<p>With that said, the whole thing took around half a day, and I was fairly happy with the setup.</p>

<p>After the migration I did get some error reports with iOS-specific <code class="language-plaintext highlighter-rouge">"TypeError: Load Failed"</code> errors, that I didn’t have the time to properly debug. However, scaling up the serverless capacity seemed to solve the issues. ¯\<em>(ツ)</em>/¯</p>

<p>This means that during peak season, Scaleway is a few euros more expensive per month than DigitalOcean, but peak season is almost over now, and by that time, I’m thinking I can scale the site down to 0, and save some money and electricity.</p>

<h2 id="final-status">Final Status</h2>

<p>This is the final stage of my migration away from US-based services for now. I’m not actively paying monthly money to US companies, which is good enough for me. My phone is still an Android, my debit card is still a Visa, and there’s no way to get around that.</p>

<p>But seeing as this is the last step, I figured I’d step back and take stock of how it all went.</p>

<ul>
  <li>Switching to proton has been really painless. I was never a huge power user of Dropbox or Google’s cloud services, but I did pay for both of them for storage in multiple places - and proton ended up being much cheaper.</li>
  <li>Switching my email, which was the thing I feared the most, was actually fairly simple. The import &amp; forwarding from Gmail worked pretty easily, and I don’t really miss it, apart from Proton botching a search every once in a while. I haven’t logged into my Gmail for weeks.</li>
  <li>Proton Docs is perfectly adequate for all my purposes and I don’t really miss Google Docs.</li>
  <li>I really wanted to switch my calendar over to Proton as well, but unfortunately the lack of tasks &amp; reminders is a dealbreaker to me. If they add that, I’m switching over in a heartbeat.</li>
  <li>Ecosia is actually much better than I thought it would be. Last time I tried it, particularly local queries were kinda bad, but they’re perfectly alright now! For some queries though, a lot of Ecosia results are LLM-generated slop sites, and I really wish there was some way to exclude them. Google is better at filtering those out.</li>
  <li>I really wanted mistral to be as good as Anthropics models for coding, but it just isn’t. Though for chat-based interface for common queries, it’s perfectly adequate. I never use my ChatGPT anymore, and my company has also quit its subscription.</li>
</ul>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[I’m trying to rely less on US services &amp; tech as mentioned in the first part of this series, and by this point I’m as done as I want to be.]]></summary></entry><entry><title type="html">Moving My Digital Footprint out of the United States - Part 1</title><link href="https://www.gustavwengel.dk/2026/02/01/out-of-us-1.html" rel="alternate" type="text/html" title="Moving My Digital Footprint out of the United States - Part 1" /><published>2026-02-01T00:00:00+00:00</published><updated>2026-02-01T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/2026/02/01/out-of-us-1</id><content type="html" xml:base="https://www.gustavwengel.dk/2026/02/01/out-of-us-1.html"><![CDATA[<p>I’ve never really been too keen on many of the US tech giants. I deleted my facebook back in 2019 (and shamefully re-created it again last year, to participate in a few groups), and I X’d Twitter back when it changed name.</p>

<p>Having the US president militarily threaten your kingdom certainly doesn’t make me more enthusiastic about sending my money our my data to the US, so I’m beginning a much more full migration of my digital footprint away from the United States. I’m documenting my journey here to hopefully inspire others on how to do the same<sup id="fnref:0" role="doc-noteref"><a href="#fn:0" class="footnote" rel="footnote">1</a></sup>.</p>

<p>When starting this process I needed to reflect on a few things:</p>
<ul>
  <li>I use <em>a lot of</em> US services, and often for good reasons. Whether it’s because they have a generous free tier, they’re the best you can get, or just because I’m used to them. Migrating over won’t be something I can accomplish in one day, so I’m dedicating a few hours to the process every Sunday. I hope to dedicate a blog post to each phase.</li>
  <li>This is probably going to cost money for me. I don’t think it’s necessarily going to be <em>a lot</em> of money, but I am willing to put my money where my mouth is and help strengthen the non-US tech scene.</li>
  <li>I might never be able to get away from all US technology. My personal computer still runs Windows (might switch that though), my Smartphone still runs Android, and my credit card is still a Visa. I’ll need to be pragmatic and focus my energy where I can make reasonable choices.</li>
</ul>

<h2 id="low-hanging-fruit">Low Hanging Fruit</h2>

<p>I’ve switched my search engine to <a href="https://www.ecosia.org">Ecosia</a>. If you’re already using Chrome, I can recommend switching over to the <a href="https://www.ecosia.org/browser">Ecosia browser</a>. Ecosia is a German-based company that donates all its profit to help combat climate  change.<br />
I’ll fully admit I don’t think the search results are as good as Google<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup>, particularly for local queries, but you can always prefix your search queries with <code class="language-plaintext highlighter-rouge">!g</code> and it’ll redirect to google for the tricky queries.</p>

<h2 id="first-phase">First Phase</h2>

<p>The first phase for me is switching over services that I actively pay for. The services I pay the most for are cloud file storage. I actually pay for both Google and Dropbox<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup>.<br />
I’ve moved them both over to <a href="https://proton.me/drive">Proton</a>, a privacy focused company from Switzerland. Proton also seems to offer almost a full replacement for all the Google services I actually use.</p>

<p>The process for migrating took a little while but actually wasn’t too complex:</p>

<ul>
  <li>I signed up for Proton and installed the Proton Drive application on my desktop computer</li>
  <li>I exported all of my Google data via <a href="https://takeout.google.com/">Google Takeout</a>, and downloaded the files.
    <ul>
      <li>For my photos I followed <a href="https://proton.me/support/how-to-import-from-google-photos">this workflow</a> to get them into proton drive</li>
      <li>For the rest of the Drive files, I just dropped them into a directory that Proton drive syncs with the cloud.</li>
    </ul>
  </li>
  <li>For Dropbox I already had the desktop app installed, so synchronizing over was a simple matter of copying the files into a directory that proton drive syncs to.</li>
</ul>

<p>This process wasn’t particularly difficult, but syncing many hundreds of GBs did mean I had to leave my computer running for a fair amount of time.</p>

<p>After synchronizing, I deleted most of my old / big files from both Dropbox and GDrive, and cancelled my subscriptions.</p>

<p>I was paying around 15£ per month for Dropbox (I was on a subscription much too big for what I needed), and around 4£ per month for Google One.<br />
I’ve managed to move all my files to a 15£ a month subscription to proton, that covers both me and my partner.</p>

<p>After moving these services, the only US tech service I still actively pay for is 6$ I spend on DigitalOcean every month to host a small website.<br />
In the next couple of months, I hope to move that service to <a href="https://www.scaleway.com/en/">Scaleway</a> which I’ve used before and quite like.</p>

<h2 id="next-phase">Next Phase</h2>

<p>I expect the next phase for me to be switching over my email. I’ve started setting up proton so that I can use hi@gustavwengel.dk as my primary email address.<br />
I didn’t get around to migrating over accounts or forwarding emails today, though - hopefully that’s for next time.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0" role="doc-endnote">
      <p>I don’t necessarily think switching tech is the highest impact one person can have. As an individual you might not have much power, but you can decide who you vote for, and where you spend your money. But I’m a technical person, so this seems like a natural thing for me to write about. <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>I used to use DuckDuckGo most places before, but it also has the same issues as Ecosia, that particularly local search queries don’t always work as well as you want. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I paid for Google One as it was convenient, and for Dropbox for a second backup, as I’d heard enough horror stories of people being locked out of their google accounts. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[I’ve never really been too keen on many of the US tech giants. I deleted my facebook back in 2019 (and shamefully re-created it again last year, to participate in a few groups), and I X’d Twitter back when it changed name.]]></summary></entry><entry><title type="html">Moving My Digital Footprint out of the United States - Part 2</title><link href="https://www.gustavwengel.dk/2026/02/01/out-of-us-2.html" rel="alternate" type="text/html" title="Moving My Digital Footprint out of the United States - Part 2" /><published>2026-02-01T00:00:00+00:00</published><updated>2026-02-01T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/2026/02/01/out-of-us-2</id><content type="html" xml:base="https://www.gustavwengel.dk/2026/02/01/out-of-us-2.html"><![CDATA[<p>I’m trying to rely less on US services &amp; tech, and I’m using a few hours every Sunday to migrate.<br />
I’ve mentioned why I want to do this migration in the <a href="/2026/02/01/out-of-us-1.html">first part of this series</a>.</p>

<p>This Sunday I didn’t get quite as much done as I had hoped (our dishwasher broke and I had a lot of dishes that needed washing), but I did manage to get further with migrating my email over to <a href="https://proton.me/">proton</a>, and I’ve set it up so that you can now reach me at <a href="mailto:hi@gustavwengel.dk">hi@gustavwengel.dk</a>, instead of my old Gmail.</p>

<p>I spent a bit of time digging through 1Password to see what accounts I wanted to switch over, and oh boy do I have many accounts lying around for services I never use, or haven’t used in years. I decided to switch over a handful of accounts that I’d actually be sad to lose access to, and leave the rest as ghost accounts still tied to my old Gmail.</p>

<p>As for my old email? I’m probably going to keep it around for a while. I’ve set it to redirect to my new one (took like 2 minutes), and proton helpfully offers a wizard to import contacts, calendars and emails from your Gmail to proton.</p>

<p>Changing habits is hard, and I still find myself reaching for my old mail, but hopefully having changed accounts, and replaced the Gmail app on my phone will make the muscle memory stick eventually.</p>

<p>I’ve sent my first outbound email from my new mail, and everything seems to work just dandy!</p>

<h2 id="next-phase">Next Phase</h2>

<p>Next week my plan is to try and move away from the wider Google ecosystem:</p>
<ul>
  <li>Replace GDocs with Proton Docs. I don’t imagine this will be very hard as I don’t actually use it for anything fancy, but I am uncertain about how I’ll migrate my documents.</li>
  <li>Replace my Google Calendar with the proton calendar</li>
  <li>Figure out what to do as a replacement for Google Keep</li>
</ul>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[I’m trying to rely less on US services &amp; tech, and I’m using a few hours every Sunday to migrate. I’ve mentioned why I want to do this migration in the first part of this series.]]></summary></entry><entry><title type="html">Moving My Digital Footprint out of the United States - Part 3</title><link href="https://www.gustavwengel.dk/2026/02/01/out-of-us-3.html" rel="alternate" type="text/html" title="Moving My Digital Footprint out of the United States - Part 3" /><published>2026-02-01T00:00:00+00:00</published><updated>2026-02-01T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/2026/02/01/out-of-us-3</id><content type="html" xml:base="https://www.gustavwengel.dk/2026/02/01/out-of-us-3.html"><![CDATA[<p>I’m trying to rely less on US services &amp; tech, and I’m using a few hours every Sunday to migrate.<br />
I’ve mentioned why I want to do this migration in the <a href="/2026/02/01/out-of-us-1.html">first part of this series</a>.</p>

<p>Last week I didn’t get to do anything, as I spent all Sunday setting up a self-contained solar panel circuitry with a battery and everything.<br />
I’m pretty sure it’s a hideously large solar cell for running such a small light, but it makes me happy every time I see it light up. Solar cells are super cool!</p>

<div class="img-div">
<img src="https://www.gustavwengel.dk/assets/img/solar-cell.jpg" />
Taking light from the sun, and turning it into... well more light!
</div>

<p>Anyway, enough about solar energy, let’s talk status updates of tech migrations. There’s been some good and bad things.</p>

<h2 id="what-went-well">What Went Well</h2>

<p>First off, switching email has been much easier than expected! As emails are forwarded automatically from Gmail to my new proton mail, I’m not really scared of losing an y emails, and then I just start any new conversations from the new proton-based email.</p>

<p>Running inbox zero in both emails is a little annoying, but it’s good motivation for switching stuff over (or perhaps unsubscribing from a few things). A surprising amount of services require you to write them an email to switch your email, but on the other hand, at least you <em>can</em> write them an email. I’m looking at you, horrible AI “support assistant” bots.</p>

<p>Switching over to use Proton Drive and Proton Docs instead of Google Drive &amp; Google Docs has been pretty seamless.<br />
The only hiccup has been that when you export your data via <a href="https://takeout.google.com/">Google Takeout</a> you don’t automatically get shared directories, so you have to download those manually if you need them, so I had to go through my GDrive to determine what I wanted to keep.</p>

<p>I also canceled my Netflix today. A very easy decision that I’ve been putting off for a long time, mostly due to inertia - they almost never have anything I want to watch.</p>

<h2 id="what-went-poorly">What Went Poorly</h2>

<p>I was really hoping that at this time I would have switched my Calendar away from Google, but Proton Calendar doesn’t support reminders (or tasks, as I believe Google calls them these days), which I use extensively. Seems like I’m going to be stuck with Google here a bit longer, until Proton ups their game.</p>

<p>I also wanted to try out Mistral as an LLM, specifically I had a small web scraping task that I figured I would test it out at<sup id="fnref:0" role="doc-noteref"><a href="#fn:0" class="footnote" rel="footnote">1</a></sup>. It’s, not great. It’s doing the thing the frontier models used where it’d say something was “perfect” and “fantastic”, when it doesn’t work at all, which is a tad disappointing.</p>

<p>It is however, much cheaper than e.g. a Claude Max subscription (15€ versus 100$ or 200$ a month), but it seems here you do get what you pay for.</p>

<h2 id="next-phase">Next Phase</h2>

<p>For my next phase I’m hoping to move my hosted services out of DigitalOcean, as that’s the last US service I pay actual money for.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:0" role="doc-endnote">
      <p>While I’m skeptical of LLMs for a fair amount of tasks, I feel like one-off web-scraping scripts is something they should be a perfect fit for. <a href="#fnref:0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[I’m trying to rely less on US services &amp; tech, and I’m using a few hours every Sunday to migrate. I’ve mentioned why I want to do this migration in the first part of this series.]]></summary></entry><entry><title type="html">How I Review Pull Requests</title><link href="https://www.gustavwengel.dk/2025/02/19/pr-reviewer-practices.html" rel="alternate" type="text/html" title="How I Review Pull Requests" /><published>2025-02-19T00:00:00+00:00</published><updated>2025-02-19T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/2025/02/19/pr-reviewer-practices</id><content type="html" xml:base="https://www.gustavwengel.dk/2025/02/19/pr-reviewer-practices.html"><![CDATA[<p>I’ve written quite a bit about pull requests before - covering everything from <a href="/how-thoroughly-should-you-review-pull-requests">how thoroughly you should review a pull request</a> to strategies for <a href="/how-to-not-get-blocked-while-waiting-for-code-review">avoiding being blocked by waiting for reviews</a> and making your <a href="/4-ways-to-make-your-pull-requests-faster-to-review">PR faster to review</a>. This post builds on those, focusing specifically on what <strong>I</strong> do when reviewing PRs.</p>

<p>I spend a fair amount of time reviewing code, and I’d like to think I’m both reasonably fast and thorough. Over time, I’ve developed some habits, that I think make this more efficient. Not all of these habits might be relevant for everyone - we’re all wired differently in our brains.</p>

<p>I also recommend checking out <a href="https://google.github.io/eng-practices">Google’s Engineering Practices</a>, which provides a short, pragmatic take on code reviews.</p>

<h2 id="read-the-pr-description-first">Read the PR Description First</h2>
<p>The first thing I do when reviewing a PR is read the description. A good description should explain <strong>what</strong> is being changed and, more importantly, <strong>why</strong>. If I don’t understand the intent behind a PR, I can’t judge whether it’s the right change to make.</p>

<p>A test I often use: if I revisit this PR in six months, will the git history tell me what I need to know? I prefer all relevant context to be included directly in the PR description rather than relying on external issue trackers like Linear, Jira, or GitHub issues. Git logs don’t decay over time, but if your company changes issue tracker - good luck finding out why the changes described in <code class="language-plaintext highlighter-rouge">JIRA-3481</code> were made.</p>

<p>If the PR description isn’t clear or doesn’t justify the change, that’s my first point of feedback. I also often check whether the code aligns with the description, and provide feedback on that.</p>

<h2 id="keep-a-notepad">Keep a Notepad</h2>
<p>While reviewing, I almost always have somewhere to jot down thoughts and questions—things like:</p>

<ul>
  <li>“I don’t understand how X works with Y.”</li>
  <li>“This is different from how we normally do X - why?”</li>
  <li>“What happens if the user provides input X?”</li>
  <li>“This looks tricky - how well-tested is it?”</li>
</ul>

<p>Mostly I keep these as a list in a separate text file. This helps because questions you think of, often aren’t answered until later in the PR. Writing them down means you don’t have to worry you’ll forget them.</p>

<p>Once I finish reviewing, any unresolved notes become comments for the author—ranging from clarifying questions to suggestions for improvement.</p>

<h2 id="some-prs-are-quick-to-review">Some PRs Are Quick to Review</h2>

<p>Not all PRs require deep scrutiny. Some are quick to review, especially if they involve:</p>

<ul>
  <li>Small refactor-only changes (especially if no tests are modified).</li>
  <li>Renaming things across the codebase, e.g., renaming <code class="language-plaintext highlighter-rouge">energy</code> to <code class="language-plaintext highlighter-rouge">electricity</code>, streamlining capitalization or similar.</li>
  <li>PRs that only add additional tests or logging.</li>
  <li>Bugfix PRs that involve minimal changes and come with a test.</li>
</ul>

<p>When a PR is well-scoped and self-contained, it can sometimes be reviewed in minutes.</p>

<h2 id="some-prs-need-to-be-split-up">Some PRs Need to Be Split Up</h2>

<p>PR review time scales exponentially with size. If a PR contains multiple unrelated changes, I have to manually untangle their interactions, which takes significantly more time. I often request that PRs be split, especially if they mix refactoring with feature work or if they are difficult to review in a single pass.</p>

<p>Spending the time as an author to split up a PR, can improve the overall time to ship, because the time you spend is often less than the extra time it would take your reviewer to review the larger PR.</p>

<h2 id="pay-extra-attention-to-module-boundaries">Pay Extra Attention to Module Boundaries</h2>
<p>Code generally have parts that are public, and parts that are internal. You can consider the public part the “module boundaries”: The parts where the module interacts with the outside world.<br />
The module boundaries could be an end-user-facing HTTP API, or simply the methods your particular module exposes for other code to interact with.<br />
Often, getting the module boundaries right is more important than getting the internal details perfect. If a module boundary is well-designed and its implementation is properly tested at the level of the module boundary, it is often easy to refactor the internals later if needed.</p>

<p>When reviewing module boundaries, I pay particular attention to:</p>

<ul>
  <li>How easy it is to use from the perspective of other developers or API consumers.</li>
  <li>Whether the type system could enforce constraints that are currently left to developers to uphold manually.</li>
</ul>

<h2 id="evaluate-the-author--module-match">Evaluate the Author &amp; Module Match</h2>
<p>The level of scrutiny I apply depends on both <strong>who wrote the PR</strong> and <strong>which module it touches</strong>.</p>

<ul>
  <li>If the author is experienced and has a strong track record, I will probably still comment on many things, but I will be quicker to accept their reasons for the way the code looks.</li>
  <li>For newer developers or those unfamiliar with a codebase or a module, I generally take a little more convincing that their approach is correct, and challenge them a bit more. Both because I can’t take their good judgement for granted yet, and because I see it as a learning experience for them.</li>
  <li>If the author is the primary maintainer of a module, I often frame feedback as suggestions rather than things that must be fixed. They are the primary maintainer, so they often know best what constraints they are operating within.</li>
  <li>If the PR affects a critical or sensitive module, I generally invest more time in reviewing it thoroughly.</li>
  <li>If the module is experimental or still evolving, I’m okay with less than stellar code, since it will likely be refactored several times during development.</li>
</ul>

<p>Based on the author, I also decide <strong>when to insist on cleanup now vs. later</strong>:<br />
Some developers follow through on “I’ll clean this up in a later PR,” while others don’t.<br />
For developers that reliably perform the follow-up, I’m generally pretty lenient about when cleanup appears, and for others, I normally insist on cleanup work being done in the same PR.</p>

<h2 id="finding-the-important-changes">Finding the Important Changes</h2>
<p>Many PRs contain a mix of boilerplate updates and tricky changes. I generally start by skimming over the files and marking the boilerplate or irrelevant files as “viewed” so I can focus on the core modifications. If the author has left comments guiding the review, that’s always helpful.</p>

<p>Then I either start looking at the module boundaries or the tests for the given code, to get an idea of how the feature is used, before jumping into the implementation - but this is very much based on gut-feeling.</p>

<p>For complex PRs, I sometimes pull the code locally to explore it in an editor, but not that often.</p>

<h2 id="things-i-look-for-when-reviewing-production-code">Things I Look for When Reviewing Production Code</h2>

<p>Some issues are easy to spot with a fresh set of eyes—like leftover debug logs or unclear method names. Others require deeper thought. I generally focus on:</p>

<ul>
  <li>When first evaluating a PR, I consider whether this code is performance-sensitive. If it isn’t, I generally don’t think too deeply about performance.</li>
  <li>Is there anything that’s un-idiomatic or inconsistent for the codebase or the language? If so, I will generally ask for this to be corrected. I think conventions for how to do and name things are generally helpful, even if there are some I disagree with personally.<br />
I generally think developers should err on the side of respecting convention, even ones they disagree with. I think having a sub-optimal convention is better than having no convention at all.</li>
  <li>I look at <code class="language-plaintext highlighter-rouge">//TODO</code> comments and often add a PR comment asking if we need to track this in an issue somewhere else, but I often leave it up to the developer if they think we need to.</li>
  <li>I try to pay particular attention to stuff that’s not easily reversible, such as security issues, or bugs that would persist invalid data into the database.</li>
  <li>I’m a big fan of type systems, and if there is an invariant that needs to be upheld, I will often recommend relying on the type-system rather than relying on developer skills - we’re only human after all. Examples of this could be using an ordered data structure for data that must be ordered, using a list that <em>cannot</em> be empty, for lists that <em>should not</em> be empty, and so forth.</li>
</ul>

<h2 id="things-i-look-for-when-reviewing-test-code">Things I Look for When Reviewing Test Code</h2>
<p>I generally pay less attention to test code than production code. My main checks are:</p>

<ul>
  <li>Do test names clearly describe what they’re testing?</li>
  <li>Do the tests cover both happy paths and unhappy paths that are either likely, or critical?</li>
  <li>Do they answer the questions in my notebook that I wrote down while reading through the implementation.</li>
</ul>

<p>If the tests cover the key scenarios I was curious about, I make a point to mention it, so reviews don’t always feel like a list of negatives.</p>

<h2 id="approve-with-suggestions">Approve with Suggestions</h2>

<p>In many cases, I like to “Approve with suggestions”, or as Google calls it a <a href="https://google.github.io/eng-practices/review/reviewer/speed.html#lgtm-with-comments">LGTM with comments</a>.<br />
That essentially means that I trust the author to correct the issues I pointed out. However, this depends on the fixes required, and my trust in the author to get them right without an additional review.</p>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[I’ve written quite a bit about pull requests before - covering everything from how thoroughly you should review a pull request to strategies for avoiding being blocked by waiting for reviews and making your PR faster to review. This post builds on those, focusing specifically on what I do when reviewing PRs.]]></summary></entry><entry><title type="html">Thoughts on Product Roadmaps</title><link href="https://www.gustavwengel.dk/2024/12/10/thoughts-on-roadmaps.html" rel="alternate" type="text/html" title="Thoughts on Product Roadmaps" /><published>2024-12-10T00:00:00+00:00</published><updated>2024-12-10T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/2024/12/10/thoughts-on-roadmaps</id><content type="html" xml:base="https://www.gustavwengel.dk/2024/12/10/thoughts-on-roadmaps.html"><![CDATA[<p><em>Caveat: While I consider myself a product-first engineer with solid product skills and an academic background partly founded in product design and UX, I have never worked as a full-time product manager. My experience only comes from “the other side of the table”, i.e. working with product managers as an engineer.</em></p>

<p>During my time at my current employer, I’ve spent a fair amount of time reflecting on product roadmaps—what they’re good for and what they might not be so good for. I think a shared understanding of priorities and collective ownership is important, and a roadmap can be a valuable tool for achieving that. Roadmaps also seem to be a reasonably standard industry practice, though there are <a href="https://basecamp.com/articles/options-not-roadmaps">some notable holdouts</a>.</p>

<h2 id="categories-of-roadmaps">Categories of Roadmaps</h2>

<p><a href="https://roadmunk.com/guides/what-is-a-product-roadmap/">Roadmunk</a> provides a solid overview of three overarching types of roadmaps:</p>

<ol>
  <li><strong>No-Dates Roadmap</strong>: A prioritized list of features or problems agreed upon by the team. Priorities can be defined in many ways (e.g., ROI, urgency/priority matrices), but this type of roadmap deliberately avoids assigning deadlines or timeframes to specific items.</li>
  <li><strong>Timeline-Based Roadmap</strong>: A roadmap where each item has a defined duration or deadline.</li>
  <li><strong>Hybrid Roadmap</strong>: Combines the two approaches, with timeframes for near-term items (e.g., 1-3 months) and a prioritized list for tasks further out.</li>
</ol>

<p>For my five cents, I think I believe the no-dates roadmap or a hybrid roadmap provides works the best. Let’s explore why.</p>

<hr />

<h2 id="the-risk-of-commitment">The Risk of Commitment</h2>

<p>The moment you introduce dates or timeframes into your roadmap, people gravitate towards treating them as commitments. The software world is rife with stories about estimates being turned into promises. I often see it recommended to keep dates out of roadmaps shared outside the immediate team:</p>

<blockquote>
  <p><em>It is not uncommon for sales reps to share internal roadmaps with customers, as a way of closing a deal, generating interest, and keeping leads warm. Avoid having sales teams committing a product to a specific release date, by excluding release or launch dates in these roadmaps.</em> - <a href="https://www.productplan.com/learn/what-is-a-product-roadmap/">ProductPlan</a></p>
</blockquote>

<blockquote>
  <p><em>An important note: avoid including hard dates in sales roadmaps to avoid tying internal teams to potentially unrealistic dates. Unless there’s certainty about the product’s availability date, it’s a good habit to avoid including dates in an external-facing roadmap.</em> - <a href="https://www.atlassian.com/agile/product-management/product-roadmaps">Atlassian</a></p>
</blockquote>

<blockquote>
  <p><em>Some schools of thought around roadmaps believe that you should keep dates out of the roadmap due to the risk of committing to something that might not be delivered</em> - <a href="https://roadmunk.com/guides/what-is-a-product-roadmap/">also Roadmunk</a></p>
</blockquote>

<p>There’s two reasons why you might be hesitant to put deadlines on roadmap items (and by extension, make some sort of commitment):</p>

<ol>
  <li><strong>Unrealistic Deadlines</strong>: Humans are notoriously bad at estimating software complexity. Unless you build in substantial slack into your timelines or do extensive time-consuming pre-planning, you’ll likely miss deadlines.</li>
  <li><strong>Building the Wrong Thing</strong>: Sometimes you realize that what you’re building doesn’t actually solve the problem you think, or even that you have another problem you’d rather focus your resources on. If you’ve commited to timeframes, you either have to renege on your promise or build the wrong thing.</li>
</ol>

<blockquote>
  <p><em>Product managers rarely know what’s going to happen a year from now (market changes, discovery of new user needs, etc.), so planning for a one-year timeline doesn’t make sense. You only need the details of the who, what and how for the month and quarter, focused on working towards achieving a high-level goal or two (for agile teams and startups, even that time frame can be a stretch!)</em> - <a href="https://roadmunk.com/guides/what-is-a-product-roadmap/">Roadmunk</a></p>
</blockquote>

<p>Both of these grow more likely the longer you try to plan your timeline, as delays in earlier tasks cascade. While you might hope to make up the difference with some tasks finishing ahead of schedule, in my experience, that’s exceedingly rare.</p>

<h2 id="flexible-commitments--appetite">Flexible Commitments &amp; Appetite</h2>
<p>There’s a natural tension between the fact that often want to communicate timeframes, either internally or externally.</p>

<p>If you do have this desire, here are some strategies to make it work:</p>

<h3 id="1-limit-the-accuracy-of-commitments">1. Limit the accuracy of commitments</h3>
<p>“We’ll be working on this in Q2 of next year” is a much weaker promise than “This will be done in April next year, with this functionality…”, often a vague commitment is “good enough”, while still leaving more room to maneuver.</p>

<h3 id="2-limit-the-timeframe-for-your-commitments">2. Limit the timeframe for your commitments.</h3>
<p>Limiting your commitments to X amount of weeks or months in the future is generally wise for two reasons. Planning enough to forecast anything with any amount of accuracy is fairly labour-intensive. I’ve seen people spend days or weeks on planning for the next 6 months, only to figure out later that they’ve been building the wrong thing and need to scrap it all.<br />
Delays also tend to cascade. If you’re only forecasting for a month, you might only be delayed for a week, while if you’re forecasting for a year you could be entire quarters off.</p>

<h3 id="3-build-plenty-of-slack-into-the-system">3. Build plenty of slack into the system</h3>
<p>Unexpected things crop up, things take longer than expected, people get sick. That’s life. If you expect 100% utilization for the new features or roadmap items you’re planning on, you’re going to be in for either a world of pain when your timetable collides with the real world, or engineers cutting corners aggressively, leading to a product so filled with technical debt it’ll remind you of the 2008 financial crisis.</p>

<h3 id="4-be-extremely-flexible-in-scope">4. Be extremely flexible in scope</h3>
<p>If you do need accuracy in time, you’ll have to be flexible in the scope. Determine a few high priority items that are must-haves, and then a prioritized list further than that, that you’re okay not delivering. Cutting down to must-haves is hard to do well, as there’s a tendency to over-estimate how much can fit in there. The list of must-haves should be so short it makes you a little uncomfortable.</p>

<h3 id="5-use-appetite-rather-than-scope">5. Use appetite rather than scope</h3>
<p>Work with the concept of <a href="(https://basecamp.com/shapeup/1.2-chapter-03)">appetite</a> rather than scope. Appetite is a concept that acknowledges that whether problems are worth solving depends on how hard they are to solve. Rather than starting with a set of features or scope, you start with a problem, and you dedicate “X weeks” of appetite to solving as much of this problem as you can. That is your appetite for the problem. It is then up to the team to try and do as much of possible to get something released for the given appetite, with whatever scope that fits into the given appetite.<br />
Obviously not all issues lead well to appetite. If you have contractual obligations to keep e.g. your database up-to-date, you cannot simply disregard this task, if it takes longer than your appetite. But I believe for most feature-based work, appetite is a really great framework that allows you to forecast timelines well into the future, at the cost of not being able to forecast scope.</p>

<hr />

<h2 id="roadmap-as-an-internal-alignment-tool">Roadmap as an Internal Alignment Tool</h2>

<p>It is important for teams and companies to agree on what problems are important to solve—and which are not. Creating a roadmap can be a catalyst for these discussions, even if the actual roadmap is never used afterward. The process of creating the roadmap creates alignment by encouraging collaboration and shared prioritization across teams.</p>

<p>As an example, sales might care more about shipping quickly to meet customer demands, while engineering often emphasizes reliability and long-term maintainability. By bringing these differing priorities to light, teams can find common ground and focus on achieving shared goals that take different priorities into account.</p>

<h2 id="when-roadmaps-become-adversarial">When Roadmaps Become Adversarial</h2>

<p>Sometimes I see roadmaps created in isolation. One team creates the first draft (or even version!) entirely on their own, and then sends it to others for feedback. This has a high risk of the process becoming adversarial, as the teams not part of the initial process will feel like they have to fight for their goals and time. There is a significantly different emotional quality to creating a roadmap together, versus having to fight for your priorities by critiquing an existing roadmap.</p>

<p>As an example, in many companies, sales-driven roadmaps pressure engineering teams into accepting unrealistic deadlines, sacrificing code quality and long-term maintainability.<br />
Situations like this, where the roadmap dictated solely by product teams or executive management can alienate stakeholders and engineers. This lack of buy-in can lead to two common reactions:</p>

<ol>
  <li>Indifference: Engineers may disengage, feeling no commitment or ownership over deadlines they had no role in setting. As a result, they might say, “If I didn’t have a say in the deadline, it’s not my problem if it isn’t met.”</li>
  <li>Stress: Unrealistic deadlines can create intense pressure, leading engineers to cut corners or burn out trying to deliver on time. Over time, this can result in a toxic environment, driving away top performers who seek healthier workplaces.</li>
</ol>

<p>In the end, roadmaps aren’t just an artifact. The way they’re created matters immensely for their effectiveness. As with so many other things that seem numbers and engineering-based in theory, they often end up being about those squishy humans in the end. I’ll leave you with this last quote which I think underlines this point well.</p>

<blockquote>
  <p>“Third, there’s the guilt. Yeah, guilt. Have you ever looked at a long list of things you said were you going to do but haven’t gotten around to yet? How does that list make you feel? The realities of life and uncertainty show us that 100% of the things on the roadmap are not going to happen on time the way we imagine.” - <a href="https://basecamp.com/articles/options-not-roadmaps">Basecamp</a></p>
</blockquote>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[Caveat: While I consider myself a product-first engineer with solid product skills and an academic background partly founded in product design and UX, I have never worked as a full-time product manager. My experience only comes from “the other side of the table”, i.e. working with product managers as an engineer.]]></summary></entry><entry><title type="html">You Should Run Cleanup Code at the Start of Your Tests</title><link href="https://www.gustavwengel.dk/cleanup-at-the-start-of-tests" rel="alternate" type="text/html" title="You Should Run Cleanup Code at the Start of Your Tests" /><published>2024-11-14T00:00:00+00:00</published><updated>2024-11-14T00:00:00+00:00</updated><id>https://www.gustavwengel.dk/cleanup-at-the-start-of-tests</id><content type="html" xml:base="https://www.gustavwengel.dk/cleanup-at-the-start-of-tests"><![CDATA[<p>I recently came across an integration test that demonstrates what I believe is an antipattern.<br />
This particular test was consistently failing at the start with a “Unique constraint violation” in the database when calling the <code class="language-plaintext highlighter-rouge">createUser</code> function.</p>

<p>The test, with irrelevant details omitted, looked like this:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// Setup</span>
    <span class="nx">someOtherSetupCode</span><span class="p">();</span>
    <span class="nx">createUser</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">TEST_ID</span><span class="dl">"</span><span class="p">});</span>
    
    <span class="c1">// Actual test code</span>

    <span class="c1">// Cleanup</span>
    <span class="nx">someOtherTeardownCode</span><span class="p">();</span>
    <span class="nx">deleteUser</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">TEST_ID</span><span class="dl">"</span><span class="p">});</span>
<span class="p">})</span>
</code></pre></div></div>

<p>This approach to organizing your test with some setup, some test code, and some cleanup at the end might seem logical.<br />
However, there’s a significant flaw: <strong>The cleanup code might never run.</strong><br />
When we put the cleanup code at the end of the test, it means it won’t run if the test fails.</p>

<p>In this case, that meant we ended up in a particularly problematic loop: if the test failed once, it could never pass without human intervention. This was because <code class="language-plaintext highlighter-rouge">createUser</code> would throw a unique constraint error if the <code class="language-plaintext highlighter-rouge">id</code> had already been used, which meant the test would not proceed, preventing the cleanup code from ever running.</p>

<h2 id="first-alternative-beforeeach-and-aftereach">First Alternative: beforeEach and afterEach</h2>
<p>A better alternative, if your testing framework supports it, is to use hooks for running code before and after each test, such as <code class="language-plaintext highlighter-rouge">beforeEach</code> and <code class="language-plaintext highlighter-rouge">afterEach</code>. This would make the test code look like this:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">beforeEach</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">someOtherSetupCode</span><span class="p">();</span>
    <span class="nx">createUser</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">TEST_ID</span><span class="dl">"</span><span class="p">});</span>
<span class="p">})</span>

<span class="nx">it</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// Actual test code</span>
<span class="p">})</span>

<span class="nx">afterEach</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">someOtherTeardownCode</span><span class="p">();</span>
    <span class="nx">deleteUser</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">TEST_ID</span><span class="dl">"</span><span class="p">});</span>
<span class="p">})</span>
</code></pre></div></div>

<p>However, this still isn’t quite optimal for a few reasons:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">deleteUser</code> call is still not guaranteed to run. It might not run if, for example, <code class="language-plaintext highlighter-rouge">someOtherTeardownCode</code> fails, or if the programmer manually interrupts the test run. This is still better than the initial example because most test frameworks run <code class="language-plaintext highlighter-rouge">afterEach</code> even if the test fails, so we avoid a scenario where the test <em>never</em> succeeds—eventually, the user model will be cleaned up.</li>
  <li>Debugging database state is challenging when you always delete models at the end. If the test fails and you suspect an issue with a database interaction, you can’t inspect the database state after the test has failed because you’ve already deleted all the relevant data.</li>
  <li>Using <code class="language-plaintext highlighter-rouge">beforeEach</code> and <code class="language-plaintext highlighter-rouge">afterEach</code> requires that all of your tests share the same state. This may be fine in many cases, but if your tests have very different setup needs, it can become cumbersome.</li>
</ul>

<h2 id="best-alternative-clean-up-first">Best Alternative: Clean Up First</h2>

<p>The best approach, in my opinion, is for each test to ensure that any data that should not exist is deleted at the <em>start</em> of the test, leaving it in the database at the end. The code would look like this:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// Clean up any potential leftover state from previous test runs</span>
    <span class="nx">someOtherTeardownCode</span><span class="p">();</span>
    <span class="nx">deleteUser</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">TEST_ID</span><span class="dl">"</span><span class="p">});</span>
    
    <span class="c1">// Ensure consistent state</span>
    <span class="nx">someOtherSetupCode</span><span class="p">();</span>
    <span class="nx">createUser</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">TEST_ID</span><span class="dl">"</span><span class="p">});</span>
    
    <span class="c1">// Actual test code</span>
<span class="p">})</span>
</code></pre></div></div>

<p>This way, you guarantee that the cleanup always runs before the test, and if you need to debug anything in the database after a test failure, all relevant data is still available for inspection.<br />
The only caveat is that your teardown code must handle both cases: when there is something to clean up and when there isn’t.</p>]]></content><author><name>Gustav Wengel</name></author><summary type="html"><![CDATA[I recently came across an integration test that demonstrates what I believe is an antipattern. This particular test was consistently failing at the start with a “Unique constraint violation” in the database when calling the createUser function.]]></summary></entry></feed>