<?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="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-02-03T14:15:18+00:00</updated><id>/feed.xml</id><title type="html">Bystams Bad Takes</title><subtitle>This is a tech blog site filled with bad-to-mediocre takes about software development.</subtitle><entry><title type="html">Are we in a software bubble?</title><link href="/takes/2026/02/02/are-we-in-a-software-bubble.html" rel="alternate" type="text/html" title="Are we in a software bubble?" /><published>2026-02-02T09:00:44+00:00</published><updated>2026-02-02T09:00:44+00:00</updated><id>/takes/2026/02/02/are-we-in-a-software-bubble</id><content type="html" xml:base="/takes/2026/02/02/are-we-in-a-software-bubble.html"><![CDATA[<p>There is something that has been bugging me for the past few months. Some kind of feeling that what I am hearing and reading is not what is truly happening. Yes, this is yet another blog post about (generative) AI. Hopefully one that contains something novel.</p>

<p>As I said: I feel like there is something brewing - something we are not talking about. This is not about founders being fraudulent or VC firms gaslighting the public, although I suspect there is some amount of that going on too. There is a lot of talk about the “AI bubble” as of late. Anecdotally, it feels like a majority of people in tech agree/suspect that there is one. Even a substantial amount of folks who love LLMs and have close to completely stopped writing code by hand will admit that “something might pop” in the near future.</p>

<p>What I am wondering, however, is whether that is missing the mark. What if we’re not in an AI bubble - <strong>but a software bubble</strong>?</p>

<h2 id="the-great-promises-of-ai">The great promises of AI</h2>

<p>LLMs and the tools built on top of them are an obvious breakthrough in digital technology. Their ability to “understand” text is unparalleled, and using interactive dialogue with the end-user to research and understand topics is a great completement to reading the internet through Google. Especially since the years leading up to the release of ChatGPT saw an absolutely horrendous enshittification of what used to be a remarkable search engine.</p>

<p>The market hype responded accordingly. Nvidia, a company that literally creates one single kind of hardware, rose to become the most valuable company in the world - and AI-native companies like Lovable are valued at <a href="https://lovable.dev/blog/series-b">ridiculous sums</a> after only a single year of existence.</p>

<p>The writing on the wall is clear: at some point in the near future, LLMs/AI will create more value for society than we can possibly imagine. Or, that’s what the investments tell us anyway.</p>

<h3 id="the-ai-sceptics">The AI sceptics</h3>

<p>The sceptics will tell you that all of this is a bubble:</p>

<ul>
  <li>LLMs are not as good as they appear. They just feel like it because they are generating text, which creates an “illusion of intelligence”.</li>
  <li>LLMs, while they can surely generate code, cannot generate good enough code at a large scale to be truly valuable.</li>
  <li>LLMs are a crutch that let people avoid learning.</li>
</ul>

<p>You have heard these takes before. Public figures bearish on AI think that the LLMs will fail to live up to the hype, and that the markets will crash. This is nothing new.</p>

<p>But that is not what is bugging me. What I am starting to wonder is this: What if there is nothing wrong with the capabilities of the LLMs. <strong>What if we are simply unable to build anything useful with them?</strong></p>

<p>Let me explain what I mean.</p>

<h3 id="the-trajectory">The trajectory</h3>

<p>Think of the significant software you use today. Then think about all the software you used 10 years ago. How much has changed? For me, not a lot. For work, I use Slack, Google Drive/Docs, Jetbrains IDEs for programming and Google Chrome for web browsing. I use Spotify for listening to music, Netflix for streaming movies and TV shows. On my smartphone, I use Facebook Messenger, WhatsApp, Twitter and Youtube.</p>

<p>In 2016, literally all of that was the same. The only new significant piece of software that has emerged in the past decade are the LLM tools.</p>

<p>Now compare that to the 10 years prior, between 2006 and 2016. In 2006, most of the apps listed either did not exist or were in their infancy. Smartphones had not yet hit the mainstream, there were no app stores. Google had not yet released a web browser.</p>

<p>TikTok is an obvious omission from my list (since I don’t use it) that actually was released in the past 10 years. But with the evidence that is connecting social media like TikTok with severe mental health problems in kids, and with the US congress even going so far as considering it a <a href="https://en.wikipedia.org/wiki/Protecting_Americans_from_Foreign_Adversary_Controlled_Applications_Act">serious threat to national security</a>, can we really call it useful?</p>

<p>If we look at the last 20 years, there seems to be an extremely visible plateau in the production of useful software. It appears that all our ideas for creating a better world using computers have stagnated. Famous software companies have either gone down the path of Microsoft and their <a href="https://x.com/WindowsLatest/status/1991952313217638540?lang=en">laughably</a> <a href="https://x.com/pubity/status/2013000854970745049?s=46">bad</a> <a href="https://x.com/MoiDawg/status/2010135111774118009?s=20">start</a> with Windows 11, made extremely questionable choices like Apple with <a href="https://x.com/dcurtis/status/1988831884122407397?s=20">liquid glass</a>, or they have done like Spotify, Netflix or Slack and arguably not changed a thing in 10 years <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>Now, you might think that I am being unfair. During the first of those two decades, an entirely new computer format (mobile smartphones) hit the market, and massive online ecosystems such as social media platforms were brought to life. It is only natural that there was significantly more software being built between 2006 and 2016 than the decade after that.</p>

<p>Also - LLMs and AI, you could say, <strong>is one of those new platforms</strong>. So just you wait. In 2036, you will have replaced all your software again.</p>

<p>But… does it look like we are headed that way?</p>

<h3 id="ai-for-building-new-user-experiences">AI for building new user experiences</h3>

<p>Let us compare AI to the expansion of the internet or the release of the smartphone. The argument, then, would be that AI as a technology will enable entirely new ways to interact with computers, which will open the door to powerful, new ways to create value and improve the lives of people.</p>

<p>ChatGPT has been out for over three years now - and several of its open and closed competitors were not far behind. Have we seen that sweeping revolution in computer interactions spring to life?</p>

<p>In comparison, the iPhone was released in 2008 - and the App Store (including the tools to build your own apps) one year after that. In 2009, you had the very first opportunity to install custom apps on your phone - with apps like Spotify being available within weeks. By 2012, it felt like everyday computer interactions had radically shifted to be a mobile experience. Social media had become something you do on your smartphone, you could send money to your friends and family on the go, and various chat apps had completely replaced SMS.</p>

<p>With the exception of the actual big chatbot companies (OpenAI, Google etc) - where is all the amazing LLM powered functionality? Virtually all companies I see have tried - and for the most part the users all despise their attempts. Microsoft is being dragged in various online spaces for their attempts to squeeze Copilot into everything. Even famous AI-influencers (in the programming space) bemoan the use of AI in various products:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Every time I see an AI button in a UI somewhere I cringe and ignore it. Simultaneously I’m going nuts with my agents. Why is consumer AI so shit? <a href="https://t.co/6tVxCFCabR">https://t.co/6tVxCFCabR</a></p>&mdash; Armin Ronacher ⇌ (@mitsuhiko) <a href="https://twitter.com/mitsuhiko/status/2010406878870643086?ref_src=twsrc%5Etfw">January 11, 2026</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>I concede that the AI platforms themselves have successfully built useful products on top of these technologies. But so far, it does not appear to scale to the entire industry. Not in the ways the smartphone or the internet did, unless you are a “prompt your way to a website”-company like Lovable or Replit.</p>

<h3 id="ai-as-a-productivity-tool">AI as a productivity tool</h3>

<p>Another way people expect AI to revolutionize work is as a productivity tool. Especially as a means of generating code.</p>

<p>Sam Altman began his stardom as the face of AI by declaring his intent for AI to cure cancer or make novel discoveries in the field of physics. Despite these ambitious ideas, it looks like most frontier model companies spend the majority of their time shipping products dedicated to “agentic programming”. Tools like Claude Code have become so popular that even billioaire CEOs with business degrees seem to play around with them:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Watching Claude Code mass-murder backend tasks in seconds then spend 45 minutes misaligning a button...<br /><br />... has given me more insight into the frontend vs backend engineer wars than a decade in tech. I used to think frontend engineers were being dramatic...<br /><br />😂😂😂</p>&mdash; Sebastian Siemiatkowski (@klarnaseb) <a href="https://twitter.com/klarnaseb/status/2015159591051424096?ref_src=twsrc%5Etfw">January 24, 2026</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>Last year was a bit of a watershed moment for LLM-assisted programming. With the release of Opus 4.5 and ChatGPT 5.2, a serious amount of independently minded software influencers changed their mind from “AI cannot write good production code” to “actually AI writes most of my code now”.</p>

<p>To me it looks like the tide is turning. Maybe this is not just an astroturfed fad that will pass in a year or two. Maybe most people will truly write code using “Agents” (or whatever comes next) in the near future. Heck, what if the models get good enough to simply help all of us write just as good code as we would write by hand - but at a significantly higher pace. If that happens, then the AI hypesters will have won, right?</p>

<p>But here is where I go back to where I started this article. Look at the direction we have been headed in the past decade. Will accelerating our productivity in said direction… actually lead to a lot of valuable software?</p>

<p>Even the most crazed, bullish AI influencers online are all about how “AI can write <strong>as good code as senior developers</strong>, but faster”. I rarely hear them talk about, or expect, that LLMs would transcend human talent and surpass us when it comes to software quality. Given that LLMs are trained on all of our existing code, making it become significantly better seems like a tricky problem to solve - if it is even solvable with the existing model architectures.</p>

<p>So if the wildest dreams of Dario Amodei come true, and we boost our productivity writing code tenfold, are we certain we won’t just spew out even more irrelevant SaaS garbage? Even more blur animations in our Finder windows that cause even harder frame rate drops? Even more addictive, attention-hacking software that <a href="https://news.ki.se/using-social-media-may-impair-childrens-attention">ruin the brains of our kids</a> and make them <a href="https://www.cdc.gov/mmwr/volumes/73/su/su7304a3.htm">suicidal</a>?</p>

<p>If AI is the productivity powerhouse that its proponents claim - are we actually in a place where we know what we would do with it? Or has software development seriously stagnated in the past decade? Maybe Casey Muratori is right when he says that it is ironic that we managed to get an AI that replicates our programming at a time when software development standards are at an absolute rock bottom.</p>

<h2 id="learnings-from-the-internet-and-social-media">Learnings from the internet and social media</h2>

<p>While it is only tangential to my main point, I think it is fair to also compare the explosive rise of AI platforms to that of social media. There is something to be said about how the past two decades have been shaped by social media platforms such as Instagram, Twitter, TikTok, Snapschat and Youtube.</p>

<p>The companies behind all these apps have made an insane amount of money, all carried by the underlying micro-targeted advertising business model. The market value created by these companies is insane, as the software enables thousands of companies to use Google, Youtube, Facebook/Instagram etc to reach the perfect target audience for their products.</p>

<p>But there is an invisible tax that has been paid by the public, too. In the past decade, countless pieces of evidence has surfaced of the drawbacks of the algorithmic social media feeds - from <a href="https://en.wikipedia.org/wiki/Frances_Haugen">whistleblowers inside the companies</a> to research linking social media use to <a href="https://pubmed.ncbi.nlm.nih.gov/39412670/">lower life satisfaction</a> and <a href="https://academic.oup.com/joc/article/75/1/1/7907139">erosion of trust in society</a>.</p>

<p>As we speed-run our way to the AI dream world as imagined by the likes of Marc Andreessen, do we believe that the software we build today will help us reverse these trends? Or are we falling ever faster into a lonelier landscape filled with anime AI companions, robot-written blog posts and an ocean of auto-generated music on Spotify?</p>

<p>As I was typing this blog post, OpenAI literally announced their <a href="https://openai.com/index/our-approach-to-advertising-and-expanding-access/">plan to insert ads into ChatGPT</a>. Time seems to be a flat circle, and the internet still seems to have only a single kind of business model.</p>

<p><img src="/assets/ad-ceos.png" alt="senator-we-run-ads" /></p>

<h2 id="so-why-a-bubble">So why a bubble?</h2>

<p>Let’s tie it all back to the title and what I mean by a software bubble. People talk about an “AI bubble” - telling us that the models cannot possibly become as good as the hypesters tell us they will be. But to me that is missing the point. The investments and valuations are not based on how high the next generation models will reach on various benchmarks - but rather what we would be able build using them. And from what I can tell, most of the proposed usage comes down to software; either by embedding it into our products or by letting it build our products for us. Both of those are based on the assumption that software product development is in a good place, and that creating more in the same trajectory will generate some kind of human prosperity. I… am not sure I feel like that is the case.</p>

<p>Maybe there is something else here - some elephant in the room not talked about on Twitter. Maybe there are some obvious ways in which generative AI can be incorporated into weapons manufacturing and that all the investments will be justified as soon as they get a contract worth 5% of the yearly Pentagon budget.</p>

<p>If not - where will it all lead? What is the end game?</p>

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

<p>This was a mixed bag of what obviously looks like AI pessimism. I am not a doomer in the sense that I think AI will become super-intelligent and erase us from the planet. It feels more likely that it will become the next iteration of social media: it will throw the likes of Lovable to fantasy-sized IPOs, and at the same time risk making our children less educated and more miserable.</p>

<p>I am also filled with hope, however. Hope that this time, maybe it is more obvious to us what is happening. Maybe we will get tired of Suno-produced music and Clippy 2.0 early enough to reject it. From the way people react to all the new AI-based features, maybe there is hope that people start chasing truly valuable technology instead?</p>

<p>Peter Thiel once said that <a href="https://thevcfactory.com/we-wanted-flying-cars-instead-we-got-140-characters-peter-thiel/">“We wanted flying cars, instead we got 140 characters”</a>. This was a condensed version of his criticism that in the past half century, we have made laughably little progress in the “world of atoms”. Instead, everything has happened in the “world of bits”. I honestly could not agree more.</p>

<p>Maybe, if we are in a software bubble, and it bursts - this could actually be great for software development as a field. Come to think of it, in the aftermath of the dot-com bubble, the broken remains of the software industry managed to create a tremendous amount of world altering products: Wikipedia, Facebook, Youtube, Gmail, Git, Skype, Google Maps and World of Warcraft…</p>

<p>Could we become that good at building software yet again?</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Honestly - pretty based on their part. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="takes" /><summary type="html"><![CDATA[There is something that has been bugging me for the past few months. Some kind of feeling that what I am hearing and reading is not what is truly happening. Yes, this is yet another blog post about (generative) AI. Hopefully one that contains something novel.]]></summary></entry><entry><title type="html">Perfect consistency - bad, actually?</title><link href="/takes/2025/06/17/is-consistency-bad.html" rel="alternate" type="text/html" title="Perfect consistency - bad, actually?" /><published>2025-06-17T09:00:44+00:00</published><updated>2025-06-17T09:00:44+00:00</updated><id>/takes/2025/06/17/is-consistency-bad</id><content type="html" xml:base="/takes/2025/06/17/is-consistency-bad.html"><![CDATA[<p>Like all developers, I often find myself in a debate about whether we should introduce automatic formatting and linting tooling to help extinguish inconsistencies in the codebase.</p>

<p>Anybody who has written modern Javascript or Typescript knows that the web is built by people treating their <code class="language-plaintext highlighter-rouge">prettier.config.mjs</code>-files as if they were beautiful Bonsai trees. Programming without proper linting plugins to handle the preferred import order and formatting is considered as reckless as dumping radioactive waste in a local lake where kids go swimming in the summer.</p>

<p>And if you are a typical “backend developer”, surely you agree there are <strong>very good reasons</strong> for why you cannot let a “controller” have direct access to a “repository” without first crossing <em>the service layer</em>? Obviously, if some parts of your system are large enough to warrant a certain amount of architectural layers, then said layers must be applied everywhere else too? Thank God that we can enforce it with things like <a href="https://www.archunit.org/">ArchUnit</a>.</p>

<p>All of this to achieve a harmonious level of consistency across the codebase. Because said consistency <strong>obviously</strong> comes with improved readability.</p>

<h2 id="or-does-it">…or does it?</h2>

<p>As with anything related to software, there is hardly any science to back up any claims of one style being more readable than another. In fact, “best practices” in general are mostly held together by the figment of our collective imagination (although that is a blog post for another day).</p>

<p>How certain are we that consistency actually does improve our ability to comprehend what is written? I think a lot about what works for myself when I configure my editor. My preferred color schemes make code look like a Christmas tree; I want as much discrepancy between different syntactic and semantic elements as possible. Function parameters, local variables and class fields all have different colors? Love it. Global variables are in cursive? Amazing. Methods/functions are blue while member variables are white? That’s the stuff.</p>

<p>There is something about things being visually different that <a href="https://en.wikipedia.org/wiki/Thinking,_Fast_and_Slow">helps my brain subconsciously</a> make sense of it without me needing to explicitly read and think hard about the tokens. Giving different constructs of the source code a certain level of <a href="https://en.wikipedia.org/wiki/Salience_(neuroscience)">salience</a> helps me identify important things without contemplating every identifier or paranthesis.</p>

<p>Here’s how the creator of a <a href="https://dyslexiefont.com/en/">particular font for dyslexic people</a> describes what makes it simpler to read:</p>

<blockquote>
  <p>Designed by a dyslexic, the font focuses on legibility by enhancing visual distinction between characters, reducing reading complexity…</p>
</blockquote>

<p>“Visual distinction” stands out as an interesting property of the font. That such a thing would help readability does feel rather intuitive.</p>

<h2 id="could-inconsistencies-be-good-actually">Could inconsistencies be… good, actually?</h2>

<p>Do you ever feel like you’re navigating a codebase where all the modules, classes and packages are so consistent that you struggle to even tell them apart? If every single piece of CRUD comes in identically layered <code class="language-plaintext highlighter-rouge">web -&gt; service -&gt; repository -&gt; domain model</code> shapes, then the sheer weight of architectural harmony suddenly drowns out the discrepancies in the business logic.</p>

<p>But what if one didn’t need to have a single blueprint for all modules? What if every module could be slightly different, where the architectural sophistication grows from a necessity to accommodate for the unique requiements and complexity of the domain?</p>

<p>Could it possibly be quite helpful to open a package and notice that <em>“this package only has an endpoints and a model file - it must be really simple”</em>?</p>

<p>And with formatting - if certain files or modules turned out to look a little bit different - is it possible that it serves as contextual clues for the brain to tell things apart? Could certain styles help your brain identify the author of the code (your colleague), letting you more easily prime your brain to read the entire code the way they typically write it?</p>

<p>You could think of it as micro-dosing inconsistencies if you will. I’d like to think of them as the ability to navigate neighborhoods of identical houses based on what cars people have in the driveway, or how they have decided to decorate their front lawn.</p>

<h2 id="good-use-of-consistency">Good use of consistency</h2>

<p>As any <a href="https://www.oxfordlearnersdictionaries.com/definition/english/bad_1">good</a> blog poster, I obviously make straw man arguments that support a rather weak take. The obvious benefit of some kind of auto-formatter is that <strong>formatting indeed does matter</strong>. Formatting is part of the puzzle when it comes to making code comprehensible. For instance:</p>

<ul>
  <li>Lumping cohesive lines together with separating empty lines can create visible “sections” of a function body</li>
  <li>Leveraging indentation to indicate control flow (even if your curly brace-language doesn’t require it)</li>
  <li>Placing longer sequences of parameter arguments on separate lines to avoid them disappearing from the screen</li>
</ul>

<p>And if it turns out that you have a team or an organization where people simply do not spend a lot of time thinking or worrying about formatting, then an auto formatter might absolutely outperform the median outcome without it.</p>

<p>Similarly - if you have a server application where different packages of similar size and complexity have completely different styles “just because”, then that obviously can hurt one’s ability to understand it. Words have meaning (or should at least), so when someone actually does decide to call something a “repository” or a “use case”, it is indeed useful if the name is a clue towards what it actually contains. And on the flip side, if every class is a “service”, then the word “service” has no meaning.</p>

<h2 id="where-are-you-even-going-with-this">Where are you even going with this?</h2>

<p>I don’t intend to suggest that these small inconsistencies are obviously and provably better.</p>

<p>I mostly for myself wonder whether there is actually a cutoff point where the use of all these automated tools, these rules about architecture and about naming, where they start working against their stated goal. And whether we are too blind to see it, because setting up the perfect automated code analysis pipieline just feels so good.</p>

<p>What do you think?</p>]]></content><author><name></name></author><category term="takes" /><summary type="html"><![CDATA[Like all developers, I often find myself in a debate about whether we should introduce automatic formatting and linting tooling to help extinguish inconsistencies in the codebase.]]></summary></entry><entry><title type="html">Kotlin extension functions are (mostly) bad</title><link href="/takes/2024/07/18/kotlin-extension-functions-bad.html" rel="alternate" type="text/html" title="Kotlin extension functions are (mostly) bad" /><published>2024-07-18T09:00:44+00:00</published><updated>2024-07-18T09:00:44+00:00</updated><id>/takes/2024/07/18/kotlin-extension-functions-bad</id><content type="html" xml:base="/takes/2024/07/18/kotlin-extension-functions-bad.html"><![CDATA[<p>Here we go! Clickbait headline right out the gate. There is nothing inherently wrong with how extension functions in Kotlin work or behave. However, I find we can talk about one aspect of Kotlin extension functions as a proxy for what I consider to be a software development malpractice. One day I aspire to find the words to write the big blog post of my dreams on the underlying topic - but for now this tiny little Kotlin thing will do.</p>

<p>Before we go further, let me quickly demonstrate what we’re talking about. Extension functions are a neat little syntactic trick baked on top of an ugly, underlying JVM signature that makes for a nicer call site. This means you can do something like this:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// instead of this...</span>
<span class="kd">object</span> <span class="nc">AddressUtil</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">prettyString</span><span class="p">(</span><span class="n">address</span><span class="p">:</span> <span class="nc">Address</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"${address.street} ${address.streetNumber}, ${address.zipCode}"</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// ... with this butt-ugly call-site...</span>
<span class="kd">val</span> <span class="py">string</span> <span class="p">=</span> <span class="nc">AddressUtil</span><span class="p">.</span><span class="nf">prettyString</span><span class="p">(</span><span class="n">address</span><span class="p">)</span>

<span class="c1">// ... you can have this...</span>
<span class="k">fun</span> <span class="nc">Address</span><span class="p">.</span><span class="nf">prettyString</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s">"${this.street} ${this.streetNumber}, ${this.zipCode}"</span>
<span class="p">}</span>

<span class="c1">// ... so pretty! It looks like the function is actually part of the Address type</span>
<span class="kd">val</span> <span class="py">string</span> <span class="p">=</span> <span class="n">address</span><span class="p">.</span><span class="nf">prettyString</span><span class="p">()</span>
</code></pre></div></div>

<p>But this neat little syntactic trick has opened more than one of Pandora’s little boxes. There are obvious pitfalls like:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nc">String</span><span class="p">.</span><span class="nf">toLocalDate</span><span class="p">():</span> <span class="nc">LocalDate</span> <span class="p">=</span> <span class="nc">LocalDate</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>

<span class="c1">// wtf - it's so weird for `.toLocalDate()` to show up on auto-complete for ALL strings</span>
<span class="s">"zuck@facebook.com"</span><span class="p">.</span><span class="nf">toLocalDate</span><span class="p">()</span> 
</code></pre></div></div>

<p>But today I am not interested in small, ugly things like being able to parse Mark Zuckerberg’s email address into a date object. Instead I hope to persuade you that there is a larger problem that causes <strong>real</strong> damage to readability.</p>

<h2 id="pandoras-by-far-least-favorite-box">Pandora’s by far least favorite box</h2>

<p>The following insight came to me when I was visiting KotlinConf this year, and I saw a talk related to library API design in Kotlin. There was this <a href="https://youtu.be/1HK2TrBwC1o?t=694">one bit in the middle of it</a>, a comment made by the presenter, that triggered something in me. The full quote:</p>

<blockquote>
  <p>You can also add [Kotlin Extension Functions] to types that you own… And I also like to do that because it makes a clear separation between what is core in your class, and what is not. Another way to think about it is that you have classes for your data, and then you have extension functions for your code.</p>
</blockquote>

<p>After I saw it said explicitly, it dawned on me that I see this pattern <strong>a lot</strong> in my professional life.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// people seem to prefer this</span>
<span class="kd">class</span> <span class="nc">Person</span> <span class="p">{</span>
    <span class="c1">// this declaration only has the state</span>
    <span class="kd">var</span> <span class="py">address</span><span class="p">:</span> <span class="nc">Address</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="p">}</span>

<span class="c1">// and then place operations on said state elsewhere - sometimes in a SEPARATE FILE</span>
<span class="k">fun</span> <span class="nc">Person</span><span class="p">.</span><span class="nf">moveTo</span><span class="p">(</span><span class="n">newAddress</span><span class="p">:</span> <span class="nc">Address</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="n">address</span> <span class="p">=</span> <span class="n">newAddress</span>
<span class="p">}</span>

<span class="c1">// instead of just...</span>
<span class="kd">class</span> <span class="nc">Person</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">address</span><span class="p">:</span> <span class="nc">Address</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>

    <span class="c1">// boo - methods?! what is this, Java????</span>
    <span class="k">fun</span> <span class="nf">moveTo</span><span class="p">(</span><span class="n">newAddress</span><span class="p">:</span> <span class="nc">Address</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">address</span> <span class="p">=</span> <span class="n">newAddress</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When I talked to some proponents of this pattern, they tell me that what they like about it is that it becomes <strong>clean</strong>. The state is declared in one place, and the state transitions in another. This allegedly makes the code more tidy instead of everything being jumbled together. They point to the declaration of the type itself and all its state, and the pristine aesthetics of having only declared a block that resembles a struct in C.</p>

<p>Let us look a bit closer at the pros and cons.</p>

<h3 id="the-benefits">The “benefits”</h3>

<p>It is not a particularly hot or contrarian take to say that when people say “clean” what they actually mean is “code I like”. Most developers agree that it is of supreme importance that code is easy to read. Some people, of course, will make the case that code being “clean” <strong>IS</strong> what makes it readable. At the same time, the only way to actually prove that said “clean” code is actually easy to read would be to write the same program twice; once strictly with the pattern and once without, and then have several third parties try to interpret the code and somehow grade their ability to understand it.</p>

<p>The proposed benefits of this split is that it makes sense to only look at a type as <strong>what it contains</strong>, and filter away everything about <strong>what it does</strong>. This is meant to be less distracting when you simply want to know what properties something has, and vice versa.</p>

<p>Now, you might expect me to say that splitting up state and state transitions in separate files and motivating it by saying that it is “tidy”, “clean” or “nice” is doing something without real, provable benefits. That’s not enough however. I would go as far as saying that it is an emotional attachment to something that has glaringly obvious drawbacks.</p>

<h3 id="the-drawbacks">The drawbacks</h3>

<p>Splitting up state and state transitions, if you CAN keep them together, does not make any sense. It is a clear step away from writing cohesive code. <a href="https://en.wikipedia.org/wiki/Cohesion_(computer_science)">Cohesion</a> refers to the practice of squeezing things together that belong together - I struggle to find anything quite as cohesive as the state of a type and the procedures one can use to manipulate said state. They are <strong>always</strong> relevant together.</p>

<p>If you put all your code for a type in one place, then a single jump in your IDE takes you to all the things that type does. If you split them up, now you have to read multiple files at once to figure out the myriad ways the type can be interacted with.</p>

<p>Also, in this specific case, the only way for an extension function to actually mutate any state is for that state to be <strong>publicly mutable</strong>, undermining any attempt at information hiding or protection of invariants.</p>

<h3 id="programmers-are-obsessed-with-splitting-things-up">Programmers are obsessed with splitting things up</h3>

<p>Imagine being a home decorator who thinks bathrooms tend to be over-furnished. There is simply too much going on in the bathroom. But then this professional has an epiphany! If we simply put the <strong>sink</strong> and everything that goes with it in another room, we will have a super clean toilet-only space, and a pristine sink-only space. Genius! Except… you know…</p>

<p>There is simply too much focus in software development on how to split things up. Be it modules, libraries, micro-services or whatever. I personally feel like the quest for separation starts in the wrong end. We shouldn’t be looking to find what things to spread out, we should search for <strong>what we squeeze together</strong>. Few things are as satisfying as when you want to change or extend a program, and quickly discover that <em>all the things I need to touch are right here</em>. After figuring out what parts to collapse into cohesive bits, the separation reveals itself as whatever is left.</p>

<h2 id="intuition-comes-first-strategic-reasoning-second">Intuition comes first, strategic reasoning second</h2>

<p>In his book <a href="https://en.wikipedia.org/wiki/The_Righteous_Mind">The Righteous Mind</a>, psychology professor Jonathan Haidt lays out an empirically observable trait of humans; that our opinions are primarily shaped by our intuition (emotions), <strong>not</strong> our reasoning. In other words, when we dislike something - it’s not because we came to the rational conclusion that we should - it is the other way around. We have some gut instinct either to like or dislike of something, and only if we are forced to defend our position do we start making up arguments. We let our intuition guide most of our decision making, and then let our internal press secretary (the reasoning part of our brain) explain our decisions after the fact.</p>

<p>Haidt calls this relationship <strong>the Rider and the Elephant</strong>. The elephant (our intuition) will mostly decide where it goes and what it does, and the rider (strategic reasoning) for the most part has to follow. To some degree, the rider can influence and actually convice the elephant to take an action against its own will - but these are rare events. This behaviour can be seen and measured when asking people to make judgment calls about things such as politics and morality.</p>

<p>One important aspect of our intuitive mind is that it rarely is built from rational thinking. Instead, it tends to be shaped by a combination of our genetics and our surroundings. If a person you look up to as an authority figure preaches a certain ideal, you are likely to build your own intuition around said ideal - without using any specific rational thought process.</p>

<p>I believe this model to be applicable to our preferences for software design patterns as well. I think the reason people decide to carve away functions and place them elsewhere is an “it feels good in my tummy” kind of situation. It looks so neat and tidy when types only have their properties, and no ugly methods to distract the reader. People then take this intuition, this aesthetic preference, and let their reasoning brain make up rhetorical talking points for why it makes sense. These talking points are then heard by large audiences (at KotlinConf), who themselves build an intuition - and the vicious cycle repeats.</p>

<h2 id="are-there-good-uses-of-extension-functions">Are there good uses of extension functions?</h2>

<p>Absolutely. The most obvious case is the one even present in the name of the language feature: <strong>extension</strong>. Let us look at a simple example.</p>

<p>Imagine you have a module or a featureset you do not control, with some <code class="language-plaintext highlighter-rouge">User</code> type that has a <code class="language-plaintext highlighter-rouge">birthDate</code>.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// we use this, but don't control and can't change it</span>
<span class="kd">class</span> <span class="nc">User</span><span class="p">(</span>
    <span class="o">..</span><span class="p">.</span>
    <span class="kd">val</span> <span class="py">birthDate</span><span class="p">:</span> <span class="nc">LocalDate</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Now we want to have some basic business logic that checks whether or not this User is old enough to vote in elections. Let us say this requires you to be 18. In regular Java code you might be forced to do something like this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserUtil</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isOldEnoughToVote</span><span class="o">(</span><span class="nc">User</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
        <span class="kt">var</span> <span class="n">age</span> <span class="o">=</span> <span class="nc">ChronoUnit</span><span class="o">.</span><span class="na">YEARS</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getBirthDate</span><span class="o">(),</span> <span class="nc">LocalDate</span><span class="o">.</span><span class="na">now</span><span class="o">());</span>
        <span class="k">return</span> <span class="n">age</span> <span class="o">&gt;</span> <span class="mi">18</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// usage</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">castVote</span><span class="o">(</span><span class="nc">User</span> <span class="n">user</span><span class="o">,</span> <span class="nc">Party</span> <span class="n">party</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(!</span><span class="nc">UserUtil</span><span class="o">.</span><span class="na">isOldEnoughToVote</span><span class="o">(</span><span class="n">user</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Try again."</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>

<p>But - this buries this logic check behind a <code class="language-plaintext highlighter-rouge">Util</code> type that one simply needs to know is there. Sure, maybe you will stumble upon it elsewhere and remember that it exists, but we can do better.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// you can also have "extension vals"</span>
<span class="kd">val</span> <span class="py">User</span><span class="p">.</span><span class="n">isOldEnoughToVote</span><span class="p">:</span> <span class="nc">Boolean</span>
    <span class="k">get</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">age</span> <span class="p">=</span> <span class="nc">ChronoUnit</span><span class="p">.</span><span class="nc">YEARS</span><span class="p">.</span><span class="nf">between</span><span class="p">(</span><span class="n">birthDate</span><span class="p">,</span> <span class="nc">LocalDate</span><span class="p">.</span><span class="nf">now</span><span class="p">())</span>
        <span class="k">return</span> <span class="n">age</span> <span class="p">&gt;</span> <span class="mi">18</span>
    <span class="p">}</span>

<span class="c1">// usage</span>
<span class="k">fun</span> <span class="nf">castVote</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="nc">User</span><span class="p">,</span> <span class="n">party</span><span class="p">:</span> <span class="nc">Party</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(!</span><span class="n">user</span><span class="p">.</span><span class="n">isOldEnoughToVote</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="nc">IllegalArgumentException</span><span class="p">(</span><span class="s">"Try again."</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="o">..</span><span class="p">.</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This might not look like much - but there is one pretty amazing upside. <strong>It shows up in auto completion</strong>. This successfully lets your addition be <strong>closer</strong> and thus more cohesive with the <code class="language-plaintext highlighter-rouge">User</code> model.</p>

<p><img src="/assets/kt-ext-code-completion.png" alt="Code completion" /></p>

<p>People often talk about how important it is to be able to read code, but I personally would also like to stress the importance of <strong>discovering</strong> code at all. In large codebases, it can be very difficult to make changes and improvements without finding the right existing building blocks you are meant to incorporate.</p>

<h2 id="so-wait---is-this-a-rant-about-kotlin-or-about-humans-and-their-moral-tendencies">So, wait - is this a rant about Kotlin or about humans and their moral tendencies?</h2>

<p>I honestly don’t know. There is a larger story brewing within me on the theme of developers’ emotional and intuitive connections to the tools they use. Whether frameworks, languages or patterns become popular because they rationally solve a real problem - or whether they were presented at an amazing keynote by an industry celebrity that sparks an irrational wave of inspiration.</p>

<p>Today it came in the form of a rant at Kotlin - one day (if ever) it might be a long post of its own.</p>]]></content><author><name></name></author><category term="takes" /><summary type="html"><![CDATA[Here we go! Clickbait headline right out the gate. There is nothing inherently wrong with how extension functions in Kotlin work or behave. However, I find we can talk about one aspect of Kotlin extension functions as a proxy for what I consider to be a software development malpractice. One day I aspire to find the words to write the big blog post of my dreams on the underlying topic - but for now this tiny little Kotlin thing will do.]]></summary></entry><entry><title type="html">In Defense of ORMs</title><link href="/takes/2024/06/24/in-defense-of-orms.html" rel="alternate" type="text/html" title="In Defense of ORMs" /><published>2024-06-24T09:00:44+00:00</published><updated>2024-06-24T09:00:44+00:00</updated><id>/takes/2024/06/24/in-defense-of-orms</id><content type="html" xml:base="/takes/2024/06/24/in-defense-of-orms.html"><![CDATA[<p>Object Relational Mapping tools are an extremely contentious topic these days. Today, all it takes is for someone to write on a forum or walk up on a speaker stage, proclaim:</p>

<blockquote>
  <p>Don’t use an ORM - just write SQL</p>
</blockquote>

<p>and what follows will be a standing ovation, virtual or in real life.</p>

<p>In my experience, your typical ORM hater is smart, productive and very influential wherever they work. That is why it has taken me so long to figure out whether I disagree with them, or if I am simply too stupid to have come to the same conclusion.</p>

<p>It was not until I heard that at least one more influential programming influencer actually DO see them as valuable that I begin to let myself defend them intellectually.</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">highly recommended even if you don&#39;t end up using it, lots of great &amp; interesting ideas<br /><br />ActiveRecord remains my favorite ORM, very pragmatic, stays out of the way &amp; leverages OO in the right way<a href="https://t.co/OIIT10GDiw">https://t.co/OIIT10GDiw</a> <a href="https://t.co/DyeNU3Qml0">https://t.co/DyeNU3Qml0</a></p>&mdash; htmx.org / CEO of SEO (same thing) (@htmx_org) <a href="https://twitter.com/htmx_org/status/1719533526951846169?ref_src=twsrc%5Etfw">November 1, 2023</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<h2 id="the-criticism">The criticism</h2>

<p>The hatred for ORMs is not unfounded. I would never go so far as to say that anyone who says their application became simpler when they tossed it out in favour of just writing SQL strings is lying. Lots and lots of of especially Java shops have been burnt on the JPA/Hibernate stove, and you will hear them complaining about:</p>

<ul>
  <li>fighting weird, implicit <code class="language-plaintext highlighter-rouge">N+1</code> performance problems</li>
  <li>debugging unexpected flushing and cache invalidation behaviours</li>
  <li>never truly understanding the correct combination of annotations to get their many-to-many to work</li>
</ul>

<p>It is natural that they start to gag when an online guide is suggesting you pick it up “to avoid having to learn or write SQL”. But I feel like the popularized position of rejecting the entire concept of an ORM means throwing out the baby with the bathwater. I contend that the problem with ORMs is not the idea itself, but something else.</p>

<h2 id="the-problem">The problem</h2>

<p>There are a couple of actual large problems with ORMs that I will totally admit.</p>

<h3 id="use-an-orm-to-not-learn-sql">Use an ORM to not learn SQL</h3>

<p>This is a terrible idea. If you are using a relational SQL database, then learning how that SQL database works is key. Gavin King, the creator of Hibernate, even has <a href="https://www.reddit.com/r/programming/comments/2cnw8x/comment/cjhcoc7/">this to say about Hibernates relationship to its underlying database</a>:</p>

<blockquote>
  <p>Indeed, systems like Hibernate are intentionally designed as “leaky abstractions” so that it’s possible to easily mix in native SQL where necessary. The leakiness of the ORM abstraction is a feature, not a bug!</p>
</blockquote>

<p>ORMs were always meant as a tool to help expressing <strong>parts</strong> of your database interactions, namely state transitions, while intentially letting you also interact with the database in other ways where raw SQL outperforms the ORM in ease-of-use.</p>

<p>Gavin later in the same comment goes on to say:</p>

<blockquote>
  <p>I speculate that the problem is not that ORM gets in the way of using SQL, it’s rather that so many Java/C#/Ruby/Python/JavaScript developers don’t have a strong enough knowledge of, or aren’t sufficiently comfortable with, relational databases and the relational model. That is emphatically not the fault of ORM!</p>
</blockquote>

<p>I find this a little less convincing. If anything, the design of the tools are often going to encourage a specific kind of usage. If we are in a place where people frequently use ORMs in a way they were never designed to be used or, then it can of course very well be that the tool itself is not very self explanatory.</p>

<p>I suspect there might be another culprit, however…</p>

<h3 id="hibernate">Hibernate</h3>

<p>Most of the ORM complaints I have heard come from people fighting with Hibernate specifically. I have spent my time working mostly on the JVM, which means it could just be a sampling bias, but I rarely hear people complain about Rails’ ActiveRecord the way they do about JPA/Hibernate.</p>

<p>I respect this criticism. Hibernate is by no means the most obvious way to implement an ORM. Any ORM is going to have a layer of “magic” between the code and the underlying SQL, but said magic comes in different flavors.</p>

<p>The way this magic works in Hibernate/JPA is that any so-called <code class="language-plaintext highlighter-rouge">@Entity</code> in Hibernate is “observed” from the side by an <code class="language-plaintext highlighter-rouge">EntityManager</code>, where the <code class="language-plaintext highlighter-rouge">EntityManager</code> reads the annotations of the entity class and attaches the appropriate behaviour. This model deeply obfuscates what actually happens in the entity lifecycle, as all the “what actually happens” stuff is tossed far away from the actual entity definition.</p>

<p>It doesn’t help that Hibernate is perhaps a bit TOO flexible when it comes to defining relationships. Entity relationships can be both eager and lazy, and can have intricate “cascade” rulesets, which define whether creating/updating/deleting one entity should automatically trigger a similar write on another. It also has both <code class="language-plaintext highlighter-rouge">@OneToMany</code> and <code class="language-plaintext highlighter-rouge">@ElementCollection</code> annotations, which both are some kind of one-to-many, and I myself don’t really fully understand the difference.</p>

<p>The combination of all these attributes, together with the fact that all of them are “operated from afar” by an <code class="language-plaintext highlighter-rouge">EntityManager</code>, means that it is hard to create some kind of intuition regarding how it actually all works. It is of course extremely well documented (otherwise it would never work at all), but good documentation can’t compensate for an unintuitive model.</p>

<p>ActiveRecord on the other hand, and my recent JVM favorite <a href="https://jetbrains.github.io/Exposed/deep-dive-into-dao.html">Kotlin Exposed</a>, are both inheritance based models, where your ORM data models inherit from some base class, and interacting with the state of your object means said changes propagating down to some underlying mechanism. In Kotlin Exposed, the “magic” that happens in my ORM classes can be found by simply using “go to definition” in my IDE. Inheritance gets a bad rap these days, but it is in fact a pretty appealing choice for building a simple 80/20 ORM.</p>

<h3 id="spring-data-jpa">Spring Data JPA</h3>

<p>The dominant application building framework on the JVM is Spring, and more recently Spring Boot. Boot specifically (inspired by Rails) is a “convention over configuration” library which helps you set up a “sane defaults” configuration to quickly get going without having to make too many choices upfront.</p>

<p>One particularly fateful, cursed decision made early in Spring is to make JPA and Hibernate its default, and in many ways only, batteries-included library for working with the database. This is unfortunate, because it forces people to opt-out of using JPA to interact with the database, rather than opting-in to using it once they are convinced that an ORM could work for them in their domain.</p>

<p>In Spring Data JPA specifically, not only are you often using Hibernate, but Hibernate is also encapsulated inside the Domain Driven Design-inspired repository-pattern of Spring Data - which hides the <code class="language-plaintext highlighter-rouge">EntityManager</code> entirely. So not only are you dealing with the already quite obscure <code class="language-plaintext highlighter-rouge">EntityManager</code>, but now all of that hides behind another layer of repository interfaces with proxy-implementations generated at runtime.</p>

<p>I don’t think the desire to create a happy-path setup for database interaction is a bad idea, but when it comes in the form of Spring Data and JPA/Hibernate, I admit one has good reason to be disappointed.</p>

<h2 id="when-can-orms-shine">When can ORMs shine?</h2>

<p>ORMs let you express several database tables as a single object graph. For many complex <a href="https://en.wikipedia.org/wiki/Online_transaction_processing">OLTP</a> systems, the ability to encapsulate state transitions underneat a single object is a real help.</p>

<p>Imagine you have an model that encapsulates a complaint made by a customer to some company. Imagine one crucial state transition of this complaint is “closing” it, when it is considered handled by your support staff. In a real world application, changes like these are usually non-trivial. Let us imagine that we have the following things that need to happen in our database:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">complaint</code> needs to be marked as <code class="language-plaintext highlighter-rouge">closed_at = now()</code></li>
  <li>A <code class="language-plaintext highlighter-rouge">complaint_event</code> needs to be inserted expressing that it was closed</li>
  <li>If there are any <code class="language-plaintext highlighter-rouge">complaint_task</code> entries that are still unresolved, we wish to add another <code class="language-plaintext highlighter-rouge">complaint_event</code> expressing that</li>
  <li>If there are any <code class="language-plaintext highlighter-rouge">complaint_task</code> entries marked as <code class="language-plaintext highlighter-rouge">escalated</code>, we reject closing altogether</li>
</ul>

<p>Using some hypothetical ORM, the code would typically end up looking like this (using Kotlin):</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Complaint</span> <span class="p">{</span>
  <span class="k">private</span> <span class="kd">var</span> <span class="py">closedAt</span><span class="p">:</span> <span class="nc">Instant</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>

  <span class="k">private</span> <span class="kd">val</span> <span class="py">tasks</span><span class="p">:</span> <span class="nc">OneToMany</span><span class="p">&lt;</span><span class="nc">ComplaintTask</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nc">OneToMany</span><span class="p">()</span>

  <span class="k">private</span> <span class="kd">val</span> <span class="py">events</span><span class="p">:</span> <span class="nc">OneToMany</span><span class="p">&lt;</span><span class="nc">ComplaintEvent</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nc">OneToMany</span><span class="p">()</span>

  <span class="k">fun</span> <span class="nf">close</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">hasEscalated</span> <span class="p">=</span> <span class="n">tasks</span><span class="p">.</span><span class="nf">any</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">isEscalated</span> <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">hasEscalated</span><span class="p">)</span> <span class="k">throw</span> <span class="nc">IllegalStateException</span><span class="p">(</span><span class="s">"Can't close complaint with escalated tasks"</span><span class="p">)</span>

    <span class="kd">val</span> <span class="py">hasUnresolvedTasks</span> <span class="p">=</span> <span class="n">tasks</span><span class="p">.</span><span class="nf">any</span> <span class="p">{</span> <span class="p">!</span><span class="n">it</span><span class="p">.</span><span class="n">isResolved</span> <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">hasUnresolvedTasks</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">events</span> <span class="p">+=</span> <span class="nc">ComplaintEvent</span><span class="p">(</span><span class="n">complaint</span> <span class="p">=</span> <span class="k">this</span><span class="p">,</span> <span class="n">description</span> <span class="p">=</span> <span class="s">"Complaint closed with unresolved tasks"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">events</span> <span class="p">+=</span> <span class="nc">ComplaintEvent</span><span class="p">(</span><span class="n">complaint</span> <span class="p">=</span> <span class="k">this</span><span class="p">,</span> <span class="n">description</span> <span class="p">=</span> <span class="s">"Complaint closed"</span><span class="p">)</span>
    <span class="n">closedAt</span> <span class="p">=</span> <span class="nc">Instant</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>in this model, all the rules related to closing are protected inside this function. The pattern, when used like this, encourages expressing multi-table state transitions and invariants deep down on the “database type” itself, right next to the database column declarations. This achieves really high levels of <a href="https://htmx.org/essays/locality-of-behaviour/">Locality of Behaviour</a>.</p>

<p>Without the use of an ORM, the same code ends to look something like this…</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ComplaintService</span><span class="p">(</span>
    <span class="c1">// dao == "data access object"</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">dao</span><span class="p">:</span> <span class="nc">ComplaintDao</span>
<span class="p">)</span> <span class="p">{</span>

    <span class="k">fun</span> <span class="nf">closeComplaint</span><span class="p">(</span><span class="n">id</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">tasks</span> <span class="p">=</span> <span class="n">dao</span><span class="p">.</span><span class="nf">findTasksByComplaintId</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
        <span class="kd">val</span> <span class="py">hasEscalated</span> <span class="p">=</span> <span class="n">tasks</span><span class="p">.</span><span class="nf">any</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">isEscalated</span> <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">hasEscalated</span><span class="p">)</span> <span class="k">throw</span> <span class="nc">IllegalStateException</span><span class="p">(</span><span class="s">"Can't close complaint with escalated tasks"</span><span class="p">)</span>

        <span class="kd">val</span> <span class="py">hasUnresolvedTasks</span> <span class="p">=</span> <span class="n">tasks</span><span class="p">.</span><span class="nf">any</span> <span class="p">{</span> <span class="p">!</span><span class="n">it</span><span class="p">.</span><span class="n">isResolved</span> <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">hasUnresolvedTasks</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">dao</span><span class="p">.</span><span class="nf">addEvent</span><span class="p">(</span><span class="n">complaintId</span> <span class="p">=</span> <span class="n">id</span><span class="p">,</span> <span class="n">description</span> <span class="p">=</span> <span class="s">"Complaint closed with unresolved tasks"</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="n">dao</span><span class="p">.</span><span class="nf">addEvent</span><span class="p">(</span><span class="n">complaintId</span> <span class="p">=</span> <span class="n">id</span><span class="p">,</span> <span class="n">description</span> <span class="p">=</span> <span class="s">"Complaint closed"</span><span class="p">)</span>
        <span class="n">dao</span><span class="p">.</span><span class="nf">updateComplaintSetClosedAt</span><span class="p">(</span><span class="n">complaintId</span> <span class="p">=</span> <span class="n">id</span><span class="p">,</span> <span class="n">closedAt</span> <span class="p">=</span> <span class="nc">Instant</span><span class="p">.</span><span class="nf">now</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">ComplaintDao</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">database</span><span class="p">:</span> <span class="nc">Database</span>
<span class="p">)</span> <span class="p">{</span>

    <span class="k">fun</span> <span class="nf">findTasksByComplaintId</span><span class="p">(</span><span class="n">complaintId</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">ComplaintTask</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">database</span>
            <span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="s">"SELECT * FROM complaint_tasks WHERE complaint_id = ?"</span><span class="p">,</span> <span class="n">complaintId</span><span class="p">)</span>
            <span class="p">.</span><span class="n">convertTo</span><span class="p">&lt;</span><span class="nc">List</span><span class="p">&lt;</span><span class="nc">ComplaintTask</span><span class="p">&gt;&gt;()</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">addEvent</span><span class="p">(</span><span class="n">complaintId</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">description</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">database</span>
            <span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="s">"INSERT INTO complaint_event (complaint_id, description) VALUES (?, ?)"</span><span class="p">,</span> <span class="n">complaintId</span><span class="p">,</span> <span class="n">description</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">updateComplaintSetClosedAt</span><span class="p">(</span><span class="n">complaintId</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">closedAt</span><span class="p">:</span> <span class="nc">Instant</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">database</span>
            <span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="s">"UPDATE complaint_event SET closed_at = ? WHERE complaint_d = ?"</span><span class="p">,</span> <span class="n">closedAt</span><span class="p">,</span> <span class="n">complaintId</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>… if you are lucky. If you instead work with people who would rather write DAO functions for <code class="language-plaintext highlighter-rouge">hasEscalatedTasks</code> or <code class="language-plaintext highlighter-rouge">hasUnresolvedTasks</code>, then you will have even more specialized methods on he second class.</p>

<p>In a real world application, I tend to see that the latter “just write SQL” pattern tends to come in two shapes:</p>

<ul>
  <li>Either whatever code is run inside the Service just executes SQL directly…
    <ul>
      <li>This in order to avoid excessive layering (which I am a fan of!)</li>
    </ul>
  </li>
  <li>… or all common queries and updates are placed in a single DAO type like upstairs
    <ul>
      <li>This in order to have reusable database interaction functions</li>
    </ul>
  </li>
</ul>

<p>Both of these styles fail to capture the strengths of the original ORM one. Splitting things up in more layers can cause the <a href="https://en.wikipedia.org/wiki/Separation_of_concerns#HTML,_CSS,_JavaScript">“separation of concerns problem”</a> where code must be read across multiple files to be understood. The DAO facades tend to get so big and hyper-specialized that you often find yourself always adding new functions for anything you write, creating an ocean of functions that have all their filters and joins in their name like <code class="language-plaintext highlighter-rouge">findAllTasksWithOutcomeAndBlockersWhereIsNotDeletedAndComplaintId</code>.</p>

<p>But most importantly, the ORM pattern creates a single place where you more or less HAVE to add state changes to, which means you also need to review how your addition or change affects the other rules of said type. In a “just write SQL” world, it is not uncommon for a new addition to the code to simply ignore any other attempts at controlling the lifecyle of data by just adding new update functions from the side. None of these patterns make either possible or impossible, but ORMs definitely is stronger at co-locating the state itself and its changes.</p>

<h3 id="what-about-querying">What about querying?</h3>

<p>Notice how I did not mention <strong>reading</strong> data a single time in the previous section. That is on purpose. ORMs are pretty convenient when doing basic reading too. If most of your system can be expressed in terms of reading an updating singular “objects”, then using an ORM for querying works splendidly as well.</p>

<p>But virtually all people who hate ORMs with a passion can recall debugging a performance problem where they fetched multiple objects from the database using an ORM, and tried their best to query them efficiently or maybe updating some of them in batch. Suddenly, what is trivial when “just writing SQL” becomes nearly impossible with the ORM. That’s true - because ORMs are not designed to perform large, complex querying or batch processing.</p>

<blockquote>
  <p>But Fredrik, quite often one has to do that - doesn’t that mean ORMs are a bad fit almost always?</p>
</blockquote>

<p>I mean, that’s exactly why ORMs are designed as leaky abstractions by design. If you need to make complex queries, then just bypass the ORM. That’s the whole point. ORMs are there to help you express complex <strong>changes</strong> of the data, while still letting you operate freely with the database on the side. Every single ORM I have encountered bakes in raw SQL access in its public API, without having to set up a database connection from the side.</p>

<p>In the <code class="language-plaintext highlighter-rouge">Complaint</code> example of the previous section, it would make perfect sense to:</p>

<ul>
  <li>Use the ORM for interacting and working with a single complaint in your system</li>
  <li>Use SQL to generate charts and reports for open complaints</li>
</ul>

<h2 id="in-sum">In sum</h2>

<p><img src="/assets/skinner-orms.jpeg" alt="Meme" /></p>

<p>If you find yourself in a business domain where complex state transitions on singular types are rare, then it makes perfect sense to never even consider an ORM. If you deal with huge imports of mostly immutable data, batch processing of information or running complex queries on large datasets using Hibernate, then yes you are going to have a bad time.</p>

<p>But a lot of us actually DO make complex state transitions on singular data graphs. And some of us do use ORMs, and we are doing fine.</p>

<p>Also, if you just hate all ORMs with a passion, then that’s fine. Enjoy your <code class="language-plaintext highlighter-rouge">findUnresolvedPaymentsWithCustomerDataAndAccountInformationFromLastMonth</code> functions!</p>]]></content><author><name></name></author><category term="takes" /><summary type="html"><![CDATA[Object Relational Mapping tools are an extremely contentious topic these days. Today, all it takes is for someone to write on a forum or walk up on a speaker stage, proclaim:]]></summary></entry><entry><title type="html">Implicit properties of your data model</title><link href="/takes/2024/03/20/implicit-model.html" rel="alternate" type="text/html" title="Implicit properties of your data model" /><published>2024-03-20T19:49:49+00:00</published><updated>2024-03-20T19:49:49+00:00</updated><id>/takes/2024/03/20/implicit-model</id><content type="html" xml:base="/takes/2024/03/20/implicit-model.html"><![CDATA[<p>I recently listened to an interview of the author of the famous <a href="https://grugbrain.dev">“The Grug Brained Developer”</a> blog post, Carson Gross. In that interview, he reiterated a point that he also made in the original text - the quote being:</p>

<blockquote>
  <p>one day code base understandable and grug can get work done, everything good!</p>

  <p>next day impossible: complexity demon spirit has entered code and very dangerous situation!</p>
</blockquote>

<p>The “next day impossible” is not only a funny expression. In the interview he talked of the experience of seeing a system grow beyond ones ability to comprehend it in just a matter of <strong>weeks</strong>.</p>

<p>That a simple system <strong>slowly</strong> evolves into a complex one through incremental additions of features, bug fixes and tweaks is a mystery to no one. But what are the kinds of changes that can break one’s mental model of the workings of a system within just a couple of weeks? I will argue that I have an example of such a thing.</p>

<h3 id="implicit-data-modeling">Implicit data modeling</h3>
<p>One thing we constantly do when programming is creating data models - be it in APIs, in the database or elsewhere. This is the bread and butter of working as a software developer.</p>

<p>Let’s give a simplified example of a real-world scenario I have experienced (more than once!). Imagine our system has a <code class="language-plaintext highlighter-rouge">User</code> type stored in the database with an id, name and email. Imagine we don’t store any password, because the user instead logs in by getting a one-time-password sent to their email.</p>

<p>One interesting aspect of models like these are the things they convey that are not explicitly written down or reflected in some data contract - but are rather implicitly assumed. One common case with all <code class="language-plaintext highlighter-rouge">User</code>-types like these is that they were all created when somebody decided to “sign up” to our platform. That implies that all the entries in that database are customers that signed up for our service. From that, the following assumptions are probably safe to make about those people:</p>
<ul>
  <li>They have gone through some kind of signup or onboarding flow</li>
  <li>They all had to accept some kind of “terms and conditions” as part of signing up</li>
  <li>They are <strong>aware</strong> that they are in fact one of our customers</li>
</ul>

<h3 id="a-wild-feature-appears">A wild feature appears</h3>
<p>Now let us introduce a feature request. The growth side of the company believes that more people would sign up and start paying for our product if they could get a nice, free, demo of the app. Therefore, they would like to send marketing emails with a link where people can get straight into the demo mode.</p>

<p>“Easy!” says some developer. “If we simply create <code class="language-plaintext highlighter-rouge">User</code> entries for their emails, they will be able to log in. And to keep track of whether they should see the demo or the real experience we give the user a new column called <code class="language-plaintext highlighter-rouge">type</code> which is either <code class="language-plaintext highlighter-rouge">real</code> or <code class="language-plaintext highlighter-rouge">demo</code>. Let’s ship it!”</p>

<p>Even in a real world scenario, this kind of change to a model can often rapidly be built and shipped with not that much code. And at a first glance, the addition was totally backwards compatible, right? All the existing <code class="language-plaintext highlighter-rouge">User</code>s presumably had their <code class="language-plaintext highlighter-rouge">type</code> value set to <code class="language-plaintext highlighter-rouge">real</code>, and thus we wouldn’t corrupt any old users.</p>

<p>Except - what about those implicit assumptions about the model? Suddenly we have a pool of users that no longer match our existing mental model. What if:</p>
<ul>
  <li>Some team works with cross-referencing our own data with data found in external sources and uses “Do we have a <code class="language-plaintext highlighter-rouge">User</code> with that email” as a proxy for “is that email a customer of ours?” They will now have tonnes of false positives.</li>
  <li>Some team uses the changes of the user table to track growth of the company customer base? Suddenly it might look like you grew way more than anticipated. If you’re lucky you’ll catch it - but if the change is too subtle then you might show the wrong numbers to stakeholders.</li>
  <li>This size of the growth hack campaign even outnumbers the size of your customer base? What if you had 10k users and we sent 20k emails? Suddenly your user table grew by 200% and what used to be the only type of user is now in the minority. With an addition of this size, a small problem can turn into a large one if the work required to restore it is proportional to the amount of “demo users”.</li>
</ul>

<p>Adding a completely new way of creating <code class="language-plaintext highlighter-rouge">User</code> has potentially broken the mental model a lot of your colleagues had. In a sense, the change turned out not to be backwards compatible at all. To adjust for it, lots of other integration points might be forced to be updated with an extra <code class="language-plaintext highlighter-rouge">where type = ‘real’</code> filter to restore the previous behavior. If you are a small company and you quickly identified all 3-5 of those integration points - great! If you are a bigger company, and you actually are not even aware of the existence of some teams who depended on the old behavior - uh oh…! Chances are that they won’t know of your change until much later when they’re starting to see “weird errors coming from the users API”. Maybe the change in this crucial integration point has done some form of “irreversible damage”, such as accidentally sending emails to non-customers.</p>

<p>Not to mention the fact that future feature additions now have to take demo users into account. Additions that might have fit naturally into the old version of the model might suddenly feel awkward in the new one.</p>

<h3 id="what-else-could-have-been-done">What else could have been done?</h3>
<p>The original bet was that “if people could try our platform out in a demo mode, then maybe they would be impressed enough to buy it”. Another approach here would be to identify a small, isolated subset of your product and simply copy-paste it into a stateless version that can be used without logging in at all. In other words closer to something like an “interactive mockup” that looks and feels like the real deal, but is more of a temporary playground. If it turns out users would like to keep their playground-work after signing up, you could potentially “import” that data into the real model after a successful signup.</p>

<p>This might mean doing a little bit more work upfront, and it might force you to keep two UIs up to date for now. But the upside is real. This kind of solution is way less likely to “leak” to the rest of the system and teams - and if it turns out that the demo mode turned out to not work, then simply deleting this copy is dirt cheap. In contrast, cleaning up the demo users could be expensive if their presence has had ripple effects in the system.</p>

<h3 id="in-summary">In summary</h3>
<p>In my experience, it sometimes takes very little code to make a rather drastic change in a model. This category of changes, which breaks the mental picture people might have of the model, can very rapidly induce a massive amount of complexity into a system. All with a very modest change.</p>

<p>You might think that the real problem here is that people shouldn’t make implicit assumptions about data models at all. There is some truth to that, but it’s not a particularly useful opinion in a fast moving environment where everyone is doing their best. In the real world, people will make these kinds of assumptions all the time, either consciously or subconsciously. A lot of the time, such an assumption will work well enough and let a team build something useful for your customers. If you are maintainer of the part of the system that holds the <code class="language-plaintext highlighter-rouge">User</code> table, it is much cheaper for your team to think twice before shipping an expansion of your model, than it is for <strong>everyone</strong> else at the company to be paranoid about what you might do.</p>

<p><strong><em>Do with that what you will.</em></strong></p>]]></content><author><name></name></author><category term="takes" /><summary type="html"><![CDATA[I recently listened to an interview of the author of the famous “The Grug Brained Developer” blog post, Carson Gross. In that interview, he reiterated a point that he also made in the original text - the quote being:]]></summary></entry></feed>