<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Darkø Tasevski</title>
    <description>The latest articles on Forem by Darkø Tasevski (@puritanic).</description>
    <link>https://forem.com/puritanic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F56280%2Feb4aba68-4712-4490-92e9-addf0c66a177.jpeg</url>
      <title>Forem: Darkø Tasevski</title>
      <link>https://forem.com/puritanic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/puritanic"/>
    <language>en</language>
    <item>
      <title>Parkinson’s Law in Software Engineering</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Tue, 26 Aug 2025 20:16:26 +0000</pubDate>
      <link>https://forem.com/puritanic/parkinsons-law-in-software-engineering-n8m</link>
      <guid>https://forem.com/puritanic/parkinsons-law-in-software-engineering-n8m</guid>
      <description>&lt;p&gt;If you've ever finished a task at the very last minute, despite having more than enough time, you've already experienced Parkinson's Law. It's the idea that &lt;em&gt;"work expands to fill the time available for its completion."&lt;/em&gt; Cyril Northcote Parkinson first described the concept in &lt;em&gt;&lt;a href="https://web.archive.org/web/20180705215319/https://www.economist.com/news/1955/11/19/parkinsons-law" rel="noopener noreferrer"&gt;Economist&lt;/a&gt;&lt;/em&gt; in 1955, and it has since become a well-cited principle in productivity studies.&lt;/p&gt;

&lt;p&gt;If you give yourself a week to do a task, you'll take a week. If you give yourself four months, you'll still find a way to make it last four months. And in software engineering, where deadlines, sprints, and priorities shift constantly, this principle shows up everywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I've Seen It Happen First-Hand
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The API
&lt;/h3&gt;

&lt;p&gt;At one company, we started working on an API feature that should have taken two or three weeks (at most). Straightforward stuff, wire up a couple of endpoints, add tests, ship it. Instead, it dragged on for nearly four months. Why? Every week we'd add "one more thing", debating naming conventions, attaching edge-case functionality, or rewriting parts to make it "future proof™." The work didn't get harder; we just let the available time invite more work into the room.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Feature Flags
&lt;/h3&gt;

&lt;p&gt;Another time, we were tasked with removing a batch of old feature flags. On paper, this was cleanup work. A day or two, maybe a week. But because there wasn't a sharp deadline, it ballooned. We delayed, dependencies shifted, and soon those flags were entangled with new features. What could have been a tidy sweep turned into a gnarly refactor. The longer we waited, the harder it got.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flaky Tests
&lt;/h3&gt;

&lt;p&gt;At one of my previous companies, flaky tests were a recurring pain point. Initially, they should've been quick to fix, a 30-minute job here, an hour there. But when they got left for "later", suddenly they piled up into a backlog that took a whole week just for categorization. It took months to fix them all and bring the CI back to a stable state. Parkinson's Law doesn't just stretch individual tasks, it compounds when you defer maintenance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Engineers Fall Into the Trap
&lt;/h2&gt;

&lt;p&gt;The more time we &lt;em&gt;think&lt;/em&gt; we have, the more we delay starting. By the time the deadline finally feels real, we're scrambling to finish anyway. Sound familiar?&lt;/p&gt;

&lt;h3&gt;
  
  
  Procrastination Disguised as Preparation
&lt;/h3&gt;

&lt;p&gt;Without pressure, it's easy to tell yourself you're "planning" or "polishing." I've spent afternoons renaming variables and rewriting the code to be more performant and nicer when the real fix was already done an hour earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overengineering by Default
&lt;/h3&gt;

&lt;p&gt;When there's time, scope expands. A hotfix turns into a refactor. A CRUD API becomes a new framework. Sometimes we call it "doing it right", but often it's Parkinson's Law whispering in the background. This overlaps with the &lt;a href="https://en.wikipedia.org/wiki/Law_of_triviality" rel="noopener noreferrer"&gt;Law of Triviality&lt;/a&gt;, where teams spend disproportionate effort on small details while bigger issues wait.&lt;/p&gt;




&lt;h2&gt;
  
  
  Management Isn't Immune Either
&lt;/h2&gt;

&lt;p&gt;It's not just engineers who fall prey to Parkinson's Law. Managers fall into the same trap when planing sprints, setting timelines, or scheduling meetings. The same rule applies: the more time and space you give, the more things tend to expand and drag out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sprints Expand to Fit
&lt;/h3&gt;

&lt;p&gt;If you give a team a two-week sprint, the work will stretch to fill two weeks. Even tasks that could be done in three days get scheduled and discussed until they spill over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meetings
&lt;/h3&gt;

&lt;p&gt;If the calendar says 1 hour, the meeting will last 1 hour. I've seen 10-minute discussions dragged out just because the slot was there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Big Project Deadlines
&lt;/h3&gt;

&lt;p&gt;Give a team a vague "end of quarter" target, and you'll see Parkinson's Law bloom into endless scope creep, extra reviews, and "one last tweak."&lt;/p&gt;




&lt;h2&gt;
  
  
  Turning Parkinson's Law to Your Advantage
&lt;/h2&gt;

&lt;p&gt;The trick isn't fighting it, it's using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time-boxing
&lt;/h3&gt;

&lt;p&gt;At another company, I've seen how time-boxing can save a messy discovery call. A concise 25-minute agenda forces clarity that an open-ended meeting can never deliver. The same applies to coding: two hours for a spike means you actually spike instead of redesigning the system. Agile teams rely heavily on this principle, framing all work within sprints or fixed blocks (&lt;a href="https://www.atlassian.com/agile/project-management/timeboxing" rel="noopener noreferrer"&gt;Atlassian on timeboxing&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Artificial Deadlines
&lt;/h3&gt;

&lt;p&gt;When I know a task could balloon, I try to give myself an earlier "fake" deadline. For example, I'll aim to finish a feature by Wednesday, even if the sprint closes Friday. That gap is my buffer for polish, rather than panic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smaller Work Units
&lt;/h3&gt;

&lt;p&gt;Breaking a large ticket into smaller ones makes Parkinson's Law work for you. Each unit has a tighter deadline, which keeps the scope from sprawling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hackathon Constraints
&lt;/h3&gt;

&lt;p&gt;I've seen hackathons produce working prototypes in 48 hours that would have taken months in a normal sprint. Why? The constraints kill off overthinking. You don't have the luxury to debate naming conventions, you just ship.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Trade-Offs
&lt;/h2&gt;

&lt;p&gt;Of course, speed isn't always better. Some work &lt;em&gt;needs&lt;/em&gt; breathing room. Designing a distributed architecture or building core SDK APIs can't be rushed without long-term pain. The key is knowing when Parkinson's Law is helping you focus, and when it's luring you into cutting corners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Another common trap&lt;/strong&gt;: new managers who read about Parkinson's Law sometimes take it too literally. They assume that if shorter deadlines make people more efficient, then &lt;em&gt;unrealistic&lt;/em&gt; deadlines must make them even faster. In practice, this backfires. Instead of encouraging focus, it creates a culture of constant firefighting, technical debt, and burnout. Deadlines should constrain scope, not crush the team. The art is in balancing enough pressure to avoid bloat, without crossing into "death march" territory (&lt;a href="https://asana.com/resources/parkinsons-law" rel="noopener noreferrer"&gt;Asana's guide&lt;/a&gt;).  &lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Parkinson's Law isn't just a productivity idea, it's a pattern I've seen in every engineering role I've had. Whether it's a sprint's work stretching over multiple sprints, a cleanup task turning into a refactor, or a project ballooning because "we have time," the principle holds: work expands to fill the space you give it.&lt;/p&gt;

&lt;p&gt;The good news? With the right constraints, time-boxing, artificial deadlines, smaller tasks, you can flip Parkinson's Law into an advantage. The next time you're estimating a feature or planning a sprint, ask yourself: &lt;em&gt;how much time does this really deserve?&lt;/em&gt; You might find that less time gives you better results.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>management</category>
      <category>leadership</category>
    </item>
    <item>
      <title>A Practical Caching Playbook</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Thu, 21 Aug 2025 09:22:29 +0000</pubDate>
      <link>https://forem.com/puritanic/a-practical-caching-playbook-56jd</link>
      <guid>https://forem.com/puritanic/a-practical-caching-playbook-56jd</guid>
      <description>&lt;p&gt;If you've ever pushed a change to production and your browser stubbornly kept showing you the old version, you've experienced caching, probably not in a good way.  &lt;/p&gt;

&lt;p&gt;HTTP caching is one of those topics that can seem deceptively simple ("it just stores stuff locally"), but the details matter. Done right, it makes sites feel lightning-fast and reduces server load. Done wrong, it causes confusing bugs and outdated resources to linger.&lt;/p&gt;

&lt;p&gt;This isn't a deep dive into every caching strategy, it's the quick, developer-focused overview I wish I had when I first started building for the web. At the end, you'll find resources for going deeper.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Cache
&lt;/h2&gt;

&lt;p&gt;Fetching resources over the network is slow and expensive, not just in terms of latency, but also in &lt;a href="https://web.dev/http-cache/" rel="noopener noreferrer"&gt;wasted bandwidth and server strain&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;Caching lets us store a copy of a resource (HTML, CSS, JS, images, fonts, etc.) so that when it's requested again, the browser can serve it directly from local storage instead of hitting the network. This reduces load times, network traffic, and the chance that a user gets a partially loaded or broken page.&lt;/p&gt;

&lt;p&gt;The process is simple in theory:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser requests a resource.&lt;/li&gt;
&lt;li&gt;Browser checks its cache for a valid copy.&lt;/li&gt;
&lt;li&gt;If it finds one, it serves it immediately.
&lt;/li&gt;
&lt;li&gt;If not, it fetches from the server and may store a copy for next time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's not the most flexible system, you have limited control over lifetimes, but it's built into every browser and requires minimal setup to get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Browser Caching Works
&lt;/h2&gt;

&lt;p&gt;Before diving into headers and strategies, it helps to understand the mechanics of how browsers actually decide whether to serve something from cache or go back to the network.  &lt;/p&gt;

&lt;h3&gt;
  
  
  The Cache Decision Process
&lt;/h3&gt;

&lt;p&gt;When a browser requests a resource, it doesn't just blindly fetch it. Instead, it follows a decision tree that looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is the resource in cache?
   ├─ No -&amp;gt; Fetch from network, store in cache
   └─ Yes -&amp;gt; Is the cached copy fresh?
       ├─ Yes -&amp;gt; Serve from cache
       └─ No -&amp;gt; Send conditional request to server
           ├─ 304 Not Modified -&amp;gt; Use cached copy, update freshness
           └─ 200 OK -&amp;gt; Replace cached copy with new version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This process explains why you sometimes see "304 Not Modified" in DevTools: the browser already had the file, it just needed the server to confirm it was still valid.  &lt;/p&gt;

&lt;h3&gt;
  
  
  What Defines the Cache Key
&lt;/h3&gt;

&lt;p&gt;It's also worth knowing that browsers don't cache purely by URL. The cache key includes several factors:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The URL itself&lt;/strong&gt; – the primary identifier.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The HTTP method&lt;/strong&gt; – &lt;code&gt;GET&lt;/code&gt; requests are typically cacheable, while &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; are not.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;Vary&lt;/code&gt; header&lt;/strong&gt; – if present, it tells the cache to consider certain request headers (like &lt;code&gt;Accept-Encoding&lt;/code&gt; or &lt;code&gt;User-Agent&lt;/code&gt;) as part of the key.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication state&lt;/strong&gt; – private resources may be cached separately per user to avoid cross-user leaks.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding this process makes the rest of the article easier to follow: headers like &lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;ETag&lt;/code&gt;, and &lt;code&gt;Last-Modified&lt;/code&gt; plug directly into this decision-making loop, shaping whether the browser trusts what it already has or goes back to the server.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Key HTTP Response Headers
&lt;/h2&gt;

&lt;p&gt;You'll usually configure caching at the &lt;strong&gt;response header&lt;/strong&gt; level (the browser handles request headers for you). The main players:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" rel="noopener noreferrer"&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, Defines how and for how long (&lt;em&gt;in seconds, relative to the request time&lt;/em&gt;) the browser should cache a resource (e.g., &lt;code&gt;max-age=8640000&lt;/code&gt; for 100 days).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires" rel="noopener noreferrer"&gt;&lt;code&gt;Expires&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, Sets a timestamp after which the resource is considered stale. Overridden by &lt;code&gt;Cache-Control&lt;/code&gt; if both are present.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified" rel="noopener noreferrer"&gt;&lt;code&gt;Last-Modified&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, Timestamp indicating when the resource last changed; used for conditional requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag" rel="noopener noreferrer"&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, A unique identifier (often a hash) for a resource version. Any change to the file should generate a new ETag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Headers like &lt;code&gt;Cache-Control&lt;/code&gt; and &lt;code&gt;Expires&lt;/code&gt; rely on time-based freshness. &lt;code&gt;Last-Modified&lt;/code&gt; and &lt;code&gt;ETag&lt;/code&gt; use validation, the browser asks the server if the cached copy is still valid, and the server replies with a &lt;code&gt;304 Not Modified&lt;/code&gt; if nothing has changed.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# Static assets with content hashing
Cache-Control: public, max-age=31536000, immutable

# HTML pages that reference changing assets
Cache-Control: no-cache

# API responses that are user-specific
Cache-Control: private, max-age=300

# Critical resources that must be fresh
Cache-Control: no-store

# CDN-optimized content
Cache-Control: public, max-age=3600, s-maxage=86400
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Closer Look at &lt;code&gt;Cache-Control&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If there's one header that defines how caching really behaves, it's &lt;code&gt;Cache-Control&lt;/code&gt;. Think of it less as a single setting and more as a collection of knobs you can tune depending on what you're serving. The most common is &lt;code&gt;max-age&lt;/code&gt;, which tells the browser how long a file should be treated as fresh. A script with &lt;code&gt;Cache-Control: max-age=86400&lt;/code&gt; will stay valid for a full day before the browser asks the server again.&lt;/p&gt;

&lt;p&gt;Not all content should linger that long. For sensitive responses, banking APIs, for example, you'd go with &lt;code&gt;no-store&lt;/code&gt;, which prevents caching altogether. &lt;code&gt;no-cache&lt;/code&gt; is more subtle: it still allows the browser to keep a copy, but forces it to check with the server before reusing it. That makes it a good fit for HTML pages that often reference new versions of CSS or JavaScript bundles.&lt;/p&gt;

&lt;p&gt;Some directives are about what happens after a file goes stale. With &lt;code&gt;must-revalidate&lt;/code&gt;, the browser has no choice but to confirm with the server before showing the file again. By contrast, &lt;code&gt;stale-while-revalidate&lt;/code&gt; gives you a performance trick: the browser can serve the old file instantly while quietly fetching the fresh one in the background. Combined with long cache times, this creates the illusion of zero-delay updates.&lt;/p&gt;

&lt;p&gt;Another important distinction is whether content is meant for just one user or many. Marking a resource as &lt;code&gt;private&lt;/code&gt; keeps it in the user's browser only, while &lt;code&gt;public&lt;/code&gt; allows intermediaries like CDNs to cache and share it across requests. This small distinction often decides whether your API response leaks into a shared cache or stays safely scoped to the user.&lt;/p&gt;

&lt;p&gt;Finally, if you're serving versioned or hashed files that will never change, you can declare them &lt;code&gt;immutable&lt;/code&gt;. This tells the browser it doesn't even need to check back with the server once a file is cached, making it perfect for assets like fonts, image sprites, or JavaScript bundles. The directive really shines when paired with modern bundlers such as webpack, Vite, or Rollup, which generate files with unique content hashes in their names (&lt;code&gt;app.3f29c1.js&lt;/code&gt;). Each new build produces new filenames, so the browser sees them as brand-new resources. The result is the best of both worlds: current files are cached aggressively and served instantly on repeat visits, while new builds automatically invalidate the old ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Implications
&lt;/h3&gt;

&lt;p&gt;Each directive affects performance differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;immutable&lt;/code&gt;&lt;/strong&gt;: Fastest - no network requests for cached resources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;max-age&lt;/code&gt;&lt;/strong&gt;: Fast for fresh content, validation for stale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;no-cache&lt;/code&gt;&lt;/strong&gt;: Always requires validation request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;no-store&lt;/code&gt;&lt;/strong&gt;: Always requires full request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, most setups end up with a mix: long-lived, immutable caching for static assets; private or no-store for anything user-specific; and short-lived or no-cache for HTML documents. The art lies in combining these directives so that your users always get a fast response without getting stuck on outdated code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Avoiding Stale Responses
&lt;/h2&gt;

&lt;p&gt;Sometimes you don't want the browser to serve a cached file, especially right after deploying a new CSS or JavaScript bundle. In development, the quick fix is to disable caching entirely in your browser's DevTools, but in production you need a more deliberate strategy.&lt;/p&gt;

&lt;p&gt;One common approach is cache busting. Instead of reusing the same filename, you append a unique query string or, more reliably, let a bundler like webpack, Vite, or Rollup generate hashed filenames automatically (&lt;code&gt;app.3f29c1.js&lt;/code&gt;). Each new build produces new filenames, so the browser treats them as brand-new resources, sidestepping the risk of serving outdated code. Another option is to adjust cache lifetimes directly: keep shorter &lt;code&gt;max-age&lt;/code&gt; values for assets that change often, while giving rarely updated resources a much longer lifetime.&lt;/p&gt;

&lt;p&gt;In my own projects, I've leaned heavily on bundler-driven content hashing. It's simple, automatic, and works much like ETags at the HTTP level: whenever the content changes, the identifier changes with it. The result is that caches stay fresh without you having to invalidate anything manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging and Testing Caching
&lt;/h2&gt;

&lt;p&gt;One of the hardest parts of caching is that problems are often invisible, the browser quietly serves you an old file and you don't realize it until something feels "off." The fastest way to confirm what's happening is to open your browser's DevTools and check the Network tab. There, the &lt;strong&gt;Size&lt;/strong&gt; column will reveal whether a file came directly from the server or was pulled from disk or memory cache. You can also toggle the "Disable cache" option to force every request to hit the server, which is invaluable during debugging.&lt;/p&gt;

&lt;p&gt;On the command line, tools like &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;httpie&lt;/code&gt; are equally helpful. Running &lt;code&gt;curl -I https://example.com/app.js&lt;/code&gt; shows only the response headers, letting you verify whether &lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;Expires&lt;/code&gt;, or &lt;code&gt;ETag&lt;/code&gt; are set the way you expect. Adding an &lt;code&gt;If-None-Match&lt;/code&gt; or &lt;code&gt;If-Modified-Since&lt;/code&gt; header to the request simulates what the browser does during validation, and you'll know caching is working if the server replies with a simple &lt;code&gt;304 Not Modified&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a higher-level view, performance audits such as Lighthouse or PageSpeed Insights can flag assets that aren't cached efficiently. And if you're troubleshooting right after a deployment, don't forget the basics: a normal refresh may still serve cached files, while a hard refresh forces the browser to fetch fresh copies. It's a quick sanity check that often clears up "phantom bugs" before you dive deeper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging rule of thumb:&lt;/strong&gt; when a bug seems to linger mysteriously, always suspect the cache before the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Cache in Practice
&lt;/h2&gt;

&lt;p&gt;Over time I've settled on a few simple patterns that work well in most front-end projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;static assets&lt;/strong&gt; like images or fonts, I give them a long &lt;code&gt;max-age&lt;/code&gt; and serve them under hashed filenames so they can sit safely in cache for months or even years.
&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;CSS and JavaScript bundles&lt;/strong&gt;, I rely on hashed filenames too. That lets me use long cache durations while still getting instant invalidation whenever I deploy a new build.
&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;HTML&lt;/strong&gt;, I usually keep caching short, or set &lt;code&gt;no-cache&lt;/code&gt;, since it often points to fresh bundles that need to be pulled in right away.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And when something strange shows up in production? My first step is almost always to clear the cache and try again, and you'd be surprised how many "mystery bugs" vanish as soon as stale files are out of the way.&lt;/p&gt;

&lt;p&gt;Caching is one of the easiest performance wins in web development, but it's also one of the easiest ways to create subtle bugs. The trick is to be intentional: decide what can stay cached, for how long, and how updates will reach users. Get that balance right, and caching becomes an ally rather than a source of frustration. After all, the fastest network request is the one you never have to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching" rel="noopener noreferrer"&gt;MDN Web Docs: HTTP caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/http-cache/" rel="noopener noreferrer"&gt;Prevent unnecessary network requests with the HTTP Cache (web.dev)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ieftimov.com/post/conditional-http-get-fastest-requests-need-no-response-body/" rel="noopener noreferrer"&gt;Conditional HTTP GET: The fastest requests need no response body&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webpack.js.org/guides/caching/#output-filenames" rel="noopener noreferrer"&gt;Cache busting in webpack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A Case Against Abstraction</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Tue, 19 Aug 2025 07:32:18 +0000</pubDate>
      <link>https://forem.com/puritanic/a-case-against-abstraction-118o</link>
      <guid>https://forem.com/puritanic/a-case-against-abstraction-118o</guid>
      <description>&lt;p&gt;Recently, I was knee-deep in a very complex project. The problem wasn't just the size of the codebase, it was the endless forest of indirection. Factory functions, Providers, Managers, Registries, Mixins; everywhere I turned, there was another layer. Following the flow of data felt less like tracing logic and more like spelunking through a cave system with no map.  &lt;/p&gt;

&lt;p&gt;At one point, I leaned on an LLM to help me debug. And it failed. Not because the model was weak, but because the architecture was so fragmented and implicit that even an AI couldn't piece it together. Abstraction, the thing we've always leaned on to tame complexity, had itself become the source of complexity.  &lt;/p&gt;

&lt;p&gt;In this specific case, abstraction didn't clarify; it obscured. For both me and the AI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Yesterday's Cure, Today's Disease
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Sufficiently advanced abstractions are indistinguishable from obfuscation." — @raganwald&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Historically, abstraction was our main defense against cognitive overload. Humans only hold so much detail in working memory, so we built neat layers that hid what we didn't need to see. Encapsulation, state management, factories, etc. they weren't just patterns, they were survival mechanisms.&lt;/p&gt;

&lt;p&gt;But in the AI era, the tradeoffs have shifted.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We hit cognitive limits quickly, while an LLM can plow through raw detail without ever getting tired.&lt;/li&gt;
&lt;li&gt;What they can't handle is &lt;em&gt;fragmentation&lt;/em&gt;. Implicit behavior scattered across dozens of files. Context broken into 15 layers of indirection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? Abstraction no longer reduces cognitive load. It multiplies it. It takes effort that should go into problem-solving and reroutes it into archaeology.&lt;/p&gt;




&lt;h2&gt;
  
  
  Case Study: When the Russian Dolls Collapse
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The project details and code samples in this case study have been obfuscated and anonymized. The patterns, complexity, and architectural issues described here are real, but the identifiers and structures are adjusted so the example remains relatable without exposing proprietary code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To see how this plays out, here's a snapshot from a real-world TypeScript/React project I worked on. It's not a hypothetical; it's a cautionary tale of what happens when abstractions pile up unchecked.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;40,000+ files in total, ~5,800 lines in core files&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;233+ files matching abstraction patterns&lt;/strong&gt; (Providers, Managers, Factories, Handlers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20+ Redux reducers&lt;/strong&gt; with complex interdependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15+ mixin compositions&lt;/strong&gt; in the components layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State object with 80+ properties&lt;/strong&gt;, spanning UI, business logic, networking, and persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;800+ unit test files&lt;/strong&gt; with 200-400 lines each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200+ E2E test files&lt;/strong&gt; with 300-500 lines each&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Abstraction Layer Explosion
&lt;/h3&gt;

&lt;p&gt;A simple user action could bounce through a chain like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; 
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Reducer&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Manager&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not architecture, that's bureaucracy. Debugging usually requires going through at least 6 to 8 files just to locate the root cause.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example chain:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;Some&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;DataManager&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;DataProvider&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;EntityManager&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;StateManager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer existed to "decouple", but the combined effect was Russian-doll indirection where nothing was visible without peeling back four wrappers.&lt;/p&gt;




&lt;h3&gt;
  
  
  Custom Frameworks on Top of Frameworks
&lt;/h3&gt;

&lt;p&gt;The team even rolled its own inheritance system to work around Immutable.js limitations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BusinessEntity&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;InheritableImmutableRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;defaultValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;({}),&lt;/span&gt;
    &lt;span class="c1"&gt;// ... 80+ more properties&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;mergeImmutableRecordDefaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BusinessEntity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This meant every developer had to understand:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Immutable.js internals.&lt;/li&gt;
&lt;li&gt;The custom inheritance layer.&lt;/li&gt;
&lt;li&gt;The domain logic built on top.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The implicit behavior was so deep that neither humans nor LLMs could reason about it without constant back-and-forth exploration.&lt;/p&gt;




&lt;h3&gt;
  
  
  State Bloat
&lt;/h3&gt;

&lt;p&gt;The main app state was a monolith with 80+ properties across unrelated domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApplicationState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;totalContainers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;dataItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DataItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// UI state&lt;/span&gt;
  &lt;span class="nx"&gt;containerRect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Rect&lt;/span&gt;
  &lt;span class="nx"&gt;scrollbarOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;isDebugModeEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;

  &lt;span class="c1"&gt;// Business logic&lt;/span&gt;
  &lt;span class="nx"&gt;formFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormField&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Attachment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;validationErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ValidationError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// Connection state&lt;/span&gt;
  &lt;span class="nx"&gt;connectionState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConnectionState&lt;/span&gt;
  &lt;span class="nx"&gt;apiService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiService&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

  &lt;span class="c1"&gt;// ... 60+ more properties&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any change meant wading through 500+ lines and dozens of imports. New developers were paralyzed, and AI assistance was worse than useless, it would hallucinate or give superficial answers because it couldn't hold the whole structure in context.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pattern Proliferation
&lt;/h3&gt;

&lt;p&gt;Each entity type copied the most complex existing pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;FormFieldManager&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;FormFieldProvider&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;FormFieldValueManager&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;FormFieldValueProvider&lt;/span&gt;
&lt;span class="nx"&gt;DataManager&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;DataProvider&lt;/span&gt;  
&lt;span class="nx"&gt;BookmarkManager&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;BookmarkProvider&lt;/span&gt;
&lt;span class="nx"&gt;CommentManager&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;CommentProvider&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was not reusability but &lt;strong&gt;4x duplication&lt;/strong&gt; with inconsistent interfaces. Even if an LLM parsed one chain, knowledge didn't transfer to others.&lt;/p&gt;




&lt;h2&gt;
  
  
  When LLM Models Hit the Wall
&lt;/h2&gt;

&lt;p&gt;This over-abstraction wasn't just hard for me. It crippled my ability to collaborate with AI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context fragmentation&lt;/strong&gt;: A single feature spanned 20+ files and thousands of lines, more than even the largest context windows can practically handle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implicit flows&lt;/strong&gt;: State changes rippled through hidden chains like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;updateEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;entityReducer&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;EntityManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateEntity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;EntityProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncToBackend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;DataManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;StateManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifySubscribers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Multiple&lt;/span&gt; &lt;span class="nx"&gt;UI&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No LLM could trace that end-to-end without losing coherence.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scattered logic&lt;/strong&gt;: Validation in Models, error handling in Reducers, sync logic in Providers. No single place contained the truth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Observed impact:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug diagnosis took &lt;strong&gt;8-10x longer&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;LLM explanations were &lt;strong&gt;70-85% less accurate&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Refactoring suggestions were blocked by tangled dependencies.&lt;/li&gt;
&lt;li&gt;Average time to onboard a new developer: 3-4 months (vs. 2-3 weeks in cleaner codebases)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture didn't just slow humans, it actively blinded AI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test Suite Complexity and Flakiness
&lt;/h2&gt;

&lt;p&gt;You might think: "Well, maybe the AI could still piece things together from the test suite." Unfortunately, no. The tests were just as over-engineered as the production code and far more fragile.&lt;/p&gt;

&lt;p&gt;Thousands of tests existed, but instead of providing confidence, they became a constant source of pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Tests Were a Crisis
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Massive mocking requirements&lt;/strong&gt;: Testing &lt;code&gt;DataManager&lt;/code&gt; meant mocking 6+ dependencies plus an entire 80-property state tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing-dependent abstraction chains&lt;/strong&gt;: Async flows cascaded through Managers, Providers, Reducers, and event emitters. Slight variations caused flakiness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implicit synchronization&lt;/strong&gt;: Tests failed unless state propagation across 5+ abstraction layers happened within arbitrary timeouts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry culture&lt;/strong&gt;: E2E tests routinely required retries and 30-second waits, masking systemic fragility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Adding a property to DataManager required updating:&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Manager mock&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Provider mock&lt;/span&gt;
&lt;span class="c1"&gt;// 3. EntityManager mock&lt;/span&gt;
&lt;span class="c1"&gt;// 4. ReducerCallbacks mock&lt;/span&gt;
&lt;span class="c1"&gt;// 5. Component test wrappers&lt;/span&gt;
&lt;span class="c1"&gt;// 6. All fixtures&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A "simple" new feature meant updating 10-20 test files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quantified Pain
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo651sbt9elpizi4x048u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo651sbt9elpizi4x048u.png" alt=" " width="469" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~80% of test runtime&lt;/strong&gt; was spent on setup, teardown, mocking, and retries, and not actual logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging a single flaky test&lt;/strong&gt; often took 3+ hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New developers needed 2-3 months&lt;/strong&gt; before they could write reliable tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test maintenance overhead&lt;/strong&gt;: Nearly 50% of development time was spent debugging and keeping tests working.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Abstraction Actually Works
&lt;/h2&gt;

&lt;p&gt;Before we dive into solutions, let's acknowledge that abstraction isn't inherently evil. There are cases where it genuinely helps:&lt;/p&gt;

&lt;h3&gt;
  
  
  Good Abstraction Examples
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain Boundaries&lt;/strong&gt;: Separating user management from payment processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-cutting Concerns&lt;/strong&gt;: Logging, error handling, authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Algorithms&lt;/strong&gt;: When the implementation details would obscure the business logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External APIs&lt;/strong&gt;: Wrapping third-party services with consistent interfaces&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Key Difference
&lt;/h3&gt;

&lt;p&gt;Good abstraction &lt;strong&gt;reduces cognitive load&lt;/strong&gt; by hiding irrelevant details. Bad abstraction &lt;strong&gt;increases cognitive load&lt;/strong&gt; by hiding relevant details.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Humans + AI Both Need Directness
&lt;/h2&gt;

&lt;p&gt;Humans think better with clarity. AI works better with explicitness. Abstraction, when it hides more than it reveals, hurts both.&lt;/p&gt;

&lt;p&gt;In the pre-AI era, abstraction bought us simplicity. In the AI era, abstraction taxes us thrice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Humans pay in cognitive load.&lt;/li&gt;
&lt;li&gt;Machines pay in broken context.&lt;/li&gt;
&lt;li&gt;And tests pay in fragility and flakiness.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The best architecture for both is direct, explicit, domain-driven, not endlessly abstracted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Audit Abstractions Like Dependencies
&lt;/h3&gt;

&lt;p&gt;If a layer doesn't clearly earn its keep, remove it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;createDataItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two files collapsed into one. No loss of clarity. Massive gain in readability.&lt;/p&gt;




&lt;h3&gt;
  
  
  Favor Services Over Factories
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ServiceFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;dataService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The factory added nothing but indirection.&lt;/p&gt;




&lt;h3&gt;
  
  
  Architect for AI Collaboration
&lt;/h3&gt;

&lt;p&gt;Assume your pair programmer is an LLM. Flatten structures, slice state into domains, and make naming explicit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (monolithic state):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApplicationState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;dataItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DataItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UIState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConnectionState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... dozens more&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (domain slices):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UIState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SyncState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model can now read and explain one slice without drowning in an 80-property blob.&lt;/p&gt;




&lt;h3&gt;
  
  
  Stay Aware of AI's Limits
&lt;/h3&gt;

&lt;p&gt;As I wrote in &lt;a href="https://dev.to/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm"&gt;my post on mental models and prompt engineering&lt;/a&gt;, LLMs are pattern-matchers, not reasoners. They inherit bias, they hallucinate, and they choke on hidden indirection. Don't anthropomorphize them, and don't expect them to reconstruct intent buried under five layers of Providers and Managers.&lt;/p&gt;




&lt;h3&gt;
  
  
  The 5-Minute Rule
&lt;/h3&gt;

&lt;p&gt;If you can't explain what an abstraction does in 5 minutes to a new developer (or AI), it's too complex. Simplify or remove it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Abstraction Isn't Dead, But the Defaults Have Changed
&lt;/h2&gt;

&lt;p&gt;Abstraction isn't going away. It was never the enemy. The problem is that too much of it, stacked without discipline, turns into an obstacle rather than a tool. What I'm describing here isn't necessarily guidance only for the AI-assisted programming; it's simply a principle we should strive for regardless of whether we're coding alongside AI agents or not: &lt;strong&gt;clarity beats unnecessary indirection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But in the AI era, the tradeoffs have become even sharper. Every abstraction has to earn its place.&lt;/p&gt;

&lt;p&gt;Does it genuinely reduce cognitive load?&lt;br&gt;
Does it make the code more straightforward for humans, machines, and the test suite?&lt;br&gt;
Can you explain its purpose in 5 minutes or less?&lt;/p&gt;

&lt;p&gt;If the answer is no, then the abstraction is adding weight, not lifting it.&lt;/p&gt;

&lt;p&gt;This case against abstraction isn't absolute. It's contextual. But the context has shifted. With or without AI, the best code is rarely the most abstract; it's the most &lt;em&gt;direct&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Coding in the Age of AI</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Mon, 18 Aug 2025 08:22:57 +0000</pubDate>
      <link>https://forem.com/puritanic/coding-in-the-age-of-ai-355m</link>
      <guid>https://forem.com/puritanic/coding-in-the-age-of-ai-355m</guid>
      <description>&lt;p&gt;When ChatGPT first started making waves, I was skeptical. I figured it was another shiny tech fad that might be fun to play with, but wouldn't really stick in my day-to-day work. Fast forward, and now I can't imagine programming without some form of AI in my toolkit.  &lt;/p&gt;

&lt;p&gt;I'm not saying AI has replaced my skills, far from it. But it &lt;em&gt;has&lt;/em&gt; reshaped how I approach problems, especially the boring, repetitive, or purely mechanical parts of development. Here's what that looks like from my side of the keyboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before AI
&lt;/h2&gt;

&lt;p&gt;If you've been coding long enough, you know the "classic" dev cycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt;: Define the problem, sketch out solutions, argue with teammates on naming conventions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code&lt;/strong&gt;: Write everything from scratch, guided by docs and hard-earned experience.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug&lt;/strong&gt;: Stare at logs, sprinkle &lt;code&gt;console.log&lt;/code&gt; like holy water, and pray.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test&lt;/strong&gt;: Scaffold unit tests by hand, often repeating the same setup boilerplate for every new module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize&lt;/strong&gt;: Refactor with a mix of pride and dread (dread optional - unless you don't have tests).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document&lt;/strong&gt;: Write docs, knowing half the team won't read them until they're in trouble.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This worked, but it was slow. The bottlenecks were everywhere: manual lookups, repetitive boilerplate, and long debugging cycles.&lt;/p&gt;




&lt;h2&gt;
  
  
  After AI
&lt;/h2&gt;

&lt;p&gt;Now, the flow feels different.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coding feels lighter&lt;/strong&gt; because AI can autocomplete, scaffold, or suggest idiomatic patterns on the fly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging is faster&lt;/strong&gt; because I can paste an error trace and get a plausible diagnosis without trawling Stack Overflow for an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing is quicker&lt;/strong&gt;. Last week, Cursor wrote around 90% of my test stubs in seconds. I still shaped the edge cases and assertions, but the grunt work was already done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation isn't a chore&lt;/strong&gt;, because tools can generate a first draft as I go, which I then tweak.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning curves are shorter&lt;/strong&gt; when I can have an AI &lt;a href="https://dev.to/puritanic/making-new-languages-click-with-llms-20e5"&gt;walk me through a new framework&lt;/a&gt; like a patient senior dev.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I mostly use ChatGPT, Cursor, and occasionally Claude Code. The tool matters less than the workflow you wrap around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Actually Use AI in My Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Coding
&lt;/h3&gt;

&lt;p&gt;I treat AI like a pair programmer who's great at boilerplate but still needs oversight. It's amazing for repetitive patterns such as CRUD endpoints, config files, and unit test scaffolds. I keep the final say, but letting AI start the draft saves me mental energy for the tricky parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;Instead of reading a wall of stack trace alone, I throw it into AI and say, "Explain what's happening and why this might be failing." It's not always perfect (especially for legacy projects with a lot of spaghetti code), but it often points me toward the right layer of the problem faster than blind searching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;I've stopped writing all the docs from scratch. I'll have AI generate inline comments, API usage examples, or even a draft README from the code itself. Then I refine. The end result is better because I'm starting from something structured instead of a blank page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security is Still on Me
&lt;/h2&gt;

&lt;p&gt;If there's one big "don't," it's handing over sensitive code or credentials. I keep anything proprietary out of prompts unless I'm working with a trusted, privacy-conscious tool. When I &lt;em&gt;do&lt;/em&gt; need to share something delicate, I anonymize it or reduce it to the smallest reproducible snippet.&lt;/p&gt;

&lt;p&gt;Some rules I follow:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stick to reputable AI providers and disable code sharing for proprietary projects.&lt;/li&gt;
&lt;li&gt;Strip or obfuscate sensitive data before pasting code in GPT.
&lt;/li&gt;
&lt;li&gt;Keep the AI software updated to avoid vulnerabilities&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Advice If You're Just Starting
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start small&lt;/strong&gt;: use AI for a specific part of your workflow, not everything at once.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay in the loop&lt;/strong&gt;: always read and understand generated code before shipping. If something seems off, then make sure you know what and why LLM is suggesting to do something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask, don't just paste&lt;/strong&gt;: the more context you give AI, the better it performs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep learning&lt;/strong&gt;: use AI to &lt;em&gt;understand&lt;/em&gt; solutions, not just implement them.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat it like a teammate&lt;/strong&gt;: collaborate, don't outsource thinking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch out for prompt fixation&lt;/strong&gt;. LLMs are great, &lt;a href="https://dev.to/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm"&gt;but they’re not infallible&lt;/a&gt;. Don’t get stuck repeating prompts that used to work. Mix up your phrasing, try new structures, and stay reflective instead of falling into autopilot&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;AI hasn't made me obsolete. It's made me faster, more adaptable, and frankly, more willing to experiment. I still need to understand the problems I'm solving, but I can spend more of my time on architecture, design, and creative solutions instead of boilerplate and tedious debugging.&lt;/p&gt;

&lt;p&gt;In a way, AI hasn't changed &lt;em&gt;what&lt;/em&gt; programming is for me. It's still about problem-solving. It's just changed the ratio of my time between "figuring stuff out" and "getting stuff done."&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Making New Languages Click with LLMs</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Tue, 12 Aug 2025 07:11:00 +0000</pubDate>
      <link>https://forem.com/puritanic/making-new-languages-click-with-llms-20e5</link>
      <guid>https://forem.com/puritanic/making-new-languages-click-with-llms-20e5</guid>
      <description>&lt;p&gt;When I jump into a new language or framework, there's always that awkward period where I know what I want to do, but I don't yet know the way to do it. The docs help. Searching helps. But lately, I've found AI, specifically large language models, to be a surprisingly effective shortcut for getting up to speed.&lt;/p&gt;

&lt;p&gt;That's been especially true recently as I've been learning Rust from scratch and re-learning Ruby after years away. In both cases, AI has helped me bridge the gap between knowing the outcome I want and understanding the idiomatic way to get there.&lt;/p&gt;

&lt;p&gt;I'm not talking about letting AI write whole features for me and calling it a day. I use it like I'd use an experienced coworker: to explain unfamiliar syntax, walk me through tricky logic, or point out patterns I wouldn't have spotted. Here's how that looks in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prime It Like a Person
&lt;/h2&gt;

&lt;p&gt;The most significant shift for me was realizing you can't treat AI just like a search box. It works better when you talk to it like it's a person with no memory of your project, because, spoiler, it &lt;em&gt;is&lt;/em&gt; like that.  &lt;/p&gt;

&lt;p&gt;I start by giving it a role ("You're a Rust mentor helping me understand lifetime annotations in this function..."), set some rules, and provide the snippet or context I'm working with. The more specific I am, the better it answers. This is basically the "prompt library" habit—keeping go-to instructions that get me 80% of the way there for common questions.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;longest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// My prompt to AI&lt;/span&gt;
&lt;span class="s"&gt;"You’re a Rust mentor. Explain exactly why we need `'a` here
and how the compiler would complain if I removed it."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way, I get an answer that goes straight to the nuance instead of a generic "this is a function" lecture.&lt;/p&gt;

&lt;p&gt;I wrote about the &lt;a href="https://dev.to/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm"&gt;mental models&lt;/a&gt; behind prompting and some of the common traps developers (and humans) fall into, and how to avoid them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use It to Decode Syntax Quickly
&lt;/h2&gt;

&lt;p&gt;When I hit syntax sugar I don't recognize, I paste in the snippet and ask for a breakdown. I don't just want &lt;em&gt;what&lt;/em&gt; it does, I want the &lt;em&gt;"why"&lt;/em&gt; behind the choice.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;admin: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;admin: &lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I weren't used to Ruby's keyword arguments, I'd feed just this snippet to AI and say: "Walk me through what's happening here and why &lt;code&gt;admin&lt;/code&gt; has a default value."&lt;/p&gt;

&lt;p&gt;The key is to focus on small, concrete chunks of code. If I throw in a 200-line file, the explanation gets vague. But if I isolate a pattern and ask for a line-by-line walk-through, I actually learn something I can reuse.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pinpoint Where I'm Stuck in the Logic
&lt;/h2&gt;

&lt;p&gt;Sometimes I understand most of the code, but one function call or return type throws me off. This is where I frame my prompt like I'm talking to a colleague: "I get it up to here, but then I'm lost, can you tell me what's happening next?"&lt;/p&gt;

&lt;p&gt;By declaring what I &lt;em&gt;do&lt;/em&gt; know, I make it easier for the model to skip the obvious and focus on the missing piece. That's saved me from drowning in over-explained basics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ask for a Refactor, Not a Rewrite
&lt;/h2&gt;

&lt;p&gt;I'll sometimes ask AI to refactor a snippet, especially if I suspect it's inefficient or violating a language-specific convention. For example, removing an &lt;code&gt;await&lt;/code&gt; from a &lt;code&gt;for&lt;/code&gt; loop in Javascript, without breaking the logic.  &lt;/p&gt;

&lt;p&gt;I treat these as learning moments. I compare its suggestion with my own approach, figure out why it's better (or worse), and adjust. This way, I'm not blindly adopting AI output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generate Examples to Learn From
&lt;/h2&gt;

&lt;p&gt;If I'm brand new to a language, I might have AI generate a function from a detailed description I give it. Then I dissect the result, asking follow-up questions about why it chose certain patterns, or whether there's a more idiomatic way.  &lt;/p&gt;

&lt;p&gt;This isn't about having AI "do the work." It's about creating a tangible example I can explore, question, and improve. Done right, it's like having an interactive coding textbook.&lt;/p&gt;

&lt;p&gt;For Example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a Rust function that takes a list of words and returns only those longer than five letters, in alphabetical order."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;filter_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="nf"&gt;.sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;filtered&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I dissect it: Why &lt;code&gt;into_iter&lt;/code&gt; instead of &lt;code&gt;iter&lt;/code&gt;? Could it be more idiomatic with method chaining? What happens if I use &lt;code&gt;sort_unstable&lt;/code&gt;?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;AI isn't my teacher, it's my accelerator. It's not replacing documentation, real code reviews, or the need to tinker on my own. But when I use it deliberately, it helps me move through the clumsy "getting familiar" phase much faster.  &lt;/p&gt;

&lt;p&gt;If you're learning a new language, try using AI the way you'd lean on a helpful peer: ask focused questions, give it the right context, and use its answers as a springboard for your own understanding.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Thinking Clearly in Code: What Works for Me</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Mon, 11 Aug 2025 08:52:21 +0000</pubDate>
      <link>https://forem.com/puritanic/thinking-clearly-in-code-what-works-for-me-44ao</link>
      <guid>https://forem.com/puritanic/thinking-clearly-in-code-what-works-for-me-44ao</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;As a developer, I've found that staying creative isn't just for designers or product folks, it's core to solving problems, navigating ambiguity, and keeping my head clear. These four strategies help me stay mentally fresh, especially during heavy cognitive days.&lt;/p&gt;




&lt;p&gt;Creative thinking isn't about artsy vibes; it's about problem-solving under pressure, thinking in systems, and finding new angles when the obvious path doesn't cut it. Software engineering requires this kind of creativity every day. Whether I'm designing an API, debugging a race condition, or simplifying messy legacy code, the ability to think clearly and inventively is essential.&lt;/p&gt;

&lt;p&gt;Here's how I protect that part of my brain from burnout.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. I Stay (Relentlessly) Organized
&lt;/h2&gt;

&lt;p&gt;When my workspace is cluttered, my brain usually is too. Starting and ending the day with a clean desk helps me feel less scattered, especially if I've been deep in the weeds with unfamiliar code or switching contexts between projects.&lt;/p&gt;

&lt;p&gt;But it's not just about physical stuff. I usually try to plan out my week so I'm not constantly surprised by deadlines. I block off time for deep work when I can. Even just writing out three priorities each morning clears space in my head for higher-order thinking. When I feel organized, I can actually &lt;em&gt;think&lt;/em&gt;, not just react.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. I Move My Body When I Feel Mentally Jammed
&lt;/h2&gt;

&lt;p&gt;There's a direct link between physical movement and creative insight, especially when you're stuck. If I've been staring at the same function for 30 minutes, it's a signal to get up. A walk, stretching, or even standing up to refill my water bottle can trigger clarity.&lt;/p&gt;

&lt;p&gt;This isn't fluff. &lt;a href="https://news.stanford.edu/stories/2014/04/walking-vs-sitting-042414" rel="noopener noreferrer"&gt;Research&lt;/a&gt; shows regular movement improves both divergent thinking (multiple ideas) and convergent thinking (narrowing to one solution). Both are critical in engineering, especially in design discussions or debugging sessions.&lt;/p&gt;

&lt;p&gt;So yeah, sometimes "step away from the keyboard" is the best dev tip I can give myself.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. I Let Myself Experiment (Even in Code)
&lt;/h2&gt;

&lt;p&gt;Not every idea has to be "the one." Sometimes the path to a good solution is paved with deliberately bad ideas. I give myself space to sketch, spike, and ask dumb questions out loud, especially when exploring unfamiliar territory. Many times, I reach the answer by tinkering, stumbling into a working approach, and then tossing out the messy first draft to rebuild from scratch with a cleaner, more intentional design. Starting fresh helps me strip away accidental complexity and keep only the parts that actually move the solution forward.&lt;/p&gt;

&lt;p&gt;Collaboration helps here. Pairing with a teammate, rubber ducking, or just throwing a bad idea into the Slack void can surface better ones. If I'm stuck, I often reframe the problem: &lt;em&gt;What's the simplest version? Could this be two smaller problems? What if I broke this contract entirely, and what would the consequences be?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, getting creative is just about zooming out or flipping the problem upside-down.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. I Write Down Every Idea - Even the Weird Ones
&lt;/h2&gt;

&lt;p&gt;I used to trust myself to remember clever ideas that popped into my head mid-task. I don't anymore. Now I jot down everything, in Notion, sticky notes, wherever.&lt;/p&gt;

&lt;p&gt;Half of them go nowhere, but the act of capturing them helps me stay open and curious. Sometimes two scraps connect days later into something solid. Other times, writing it down clears mental space so I can focus on what's in front of me.&lt;/p&gt;

&lt;p&gt;This habit helps me capture creative “drift” while staying grounded in what I'm working on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reflection = Creativity Boost
&lt;/h2&gt;

&lt;p&gt;Every week or so, I do a &lt;a href="https://www.todoist.com/productivity-methods/weekly-review" rel="noopener noreferrer"&gt;mini-retro&lt;/a&gt;: What made me feel creative? What drained me? When did I feel most "in flow"? This gives me data about what conditions support my best thinking and how I can replicate them. This can feel like an extra task you'd rather skip, but the long-term payoff is enormous. I’ve even set up a Notion template that automatically appears at the end of the week, making it harder to ignore and easier to build the habit.&lt;/p&gt;

&lt;p&gt;In software, creative insight doesn't always look like an eureka moment. Sometimes it's a small refactor that saves hours of debugging later. Sometimes it's knowing &lt;em&gt;when not&lt;/em&gt; to ship a feature. But either way, keeping your creative brain limber is a fundamental skill worth cultivating.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Remote work, fragmented teams, and too many Slack/Teams channels can make creative thinking feel rare. But I've found it's something I can nurture with small, deliberate habits. Staying organized, moving around, experimenting freely, and capturing ideas as they come, that's what keeps me sharp.&lt;/p&gt;

&lt;p&gt;What do you do when your thinking starts to feel stale?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building Collaborative Interfaces: Operational Transforms vs. CRDTs</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Fri, 08 Aug 2025 08:45:47 +0000</pubDate>
      <link>https://forem.com/puritanic/building-collaborative-interfaces-operational-transforms-vs-crdts-2obo</link>
      <guid>https://forem.com/puritanic/building-collaborative-interfaces-operational-transforms-vs-crdts-2obo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Can we make it work like Google Docs?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's a request that sounds simple until you try to build it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Real-time collaboration is one of those features that feels like magic when it works. Two people typing into the same document, edits appearing seamlessly, no conflicts, no data loss. But under the hood, it's a carefully choreographed system of synchronization, merging, and conflict resolution.&lt;/p&gt;

&lt;p&gt;As a senior engineer, I've come to see that real-time editing isn't just a UI challenge; it's an architectural one. Whether you're building a document editor, a collaborative whiteboard, or a multiplayer code environment, you eventually face a core decision: &lt;strong&gt;how do you model and reconcile concurrent changes to the same data?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two major approaches have emerged to solve this: &lt;strong&gt;Operational Transforms (&lt;a href="https://en.wikipedia.org/wiki/Operational_transformation?useskin=vector" rel="noopener noreferrer"&gt;OT&lt;/a&gt;)&lt;/strong&gt; and &lt;strong&gt;Conflict-free Replicated Data Types (&lt;a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type?useskin=vector" rel="noopener noreferrer"&gt;CRDTs&lt;/a&gt;)&lt;/strong&gt;. Each has its philosophy, strengths, and trade-offs, and picking the right one isn't always obvious.&lt;/p&gt;

&lt;p&gt;In this post, I'll walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What OT and CRDTs are (in plain English)&lt;/li&gt;
&lt;li&gt;How do they solve the problem of collaboration&lt;/li&gt;
&lt;li&gt;Where each shines and where they fall short&lt;/li&gt;
&lt;li&gt;How to choose the right one for your architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you're building the next Notion or just curious how collaborative tech works under the hood, this post aims to give you a clear, practical foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Collaborative Editing Is Harder Than It Looks
&lt;/h2&gt;

&lt;p&gt;At a glance, real-time collaboration seems like a networking problem: sync edits between clients and you're good, right?&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;The real challenge lies in handling conflicting edits from multiple users, especially when those edits happen at the same time, in different places, or offline. Let's say two people insert text at the same position in a document. Whose change wins? In what order? And how do we ensure that everyone eventually sees the same result, without clobbering anyone's work?&lt;/p&gt;

&lt;p&gt;Some of the core problems collaborative systems need to solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt;: Multiple edits happening simultaneously on the same data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering&lt;/strong&gt;: Ensuring consistent operation ordering across clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conflict resolution&lt;/strong&gt;: Resolving divergent versions without manual merges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt;: Keeping edits fast and responsive, even over slow or flaky connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline support&lt;/strong&gt;: Handling edits made offline and merging them correctly later&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Syncing changes efficiently across many users and devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In traditional single-user apps, you can trust that changes happen in a known order and context. In collaborative systems, &lt;strong&gt;you lose that guarantee;&lt;/strong&gt; you now need to deal with partial views, out-of-order events, and race conditions between human intentions.&lt;/p&gt;

&lt;p&gt;This is where OT and CRDTs come in. Both are designed to &lt;strong&gt;ensure eventual consistency,&lt;/strong&gt; meaning that regardless of the edits made and their order, all users will end up with the same state. But they take radically different paths to get there.&lt;/p&gt;

&lt;p&gt;Let's explore how each works and what that means for you as an engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Transforms: Old School, Still Useful
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Operational Transforms (OT&lt;/strong&gt;) is one of the earliest and most battle-tested approaches to real-time collaboration. If you've used Google Docs, you've likely seen it in &lt;a href="https://youtu.be/yCcWpzY8dIA?feature=shared&amp;amp;t=300" rel="noopener noreferrer"&gt;action&lt;/a&gt;, without realizing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How It Works&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At its core, OT is based on this idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a user performs an operation (like inserting or deleting text), that operation is sent to a central server.&lt;/li&gt;
&lt;li&gt;The server receives operations from all clients, determines their order, and transforms incoming operations to account for other concurrent operations.&lt;/li&gt;
&lt;li&gt;The transformed operations are then broadcast back to all clients, ensuring that everyone consistently applies changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine two users trying to insert a character at the same position. OT resolves this by shifting one operation forward (or backward) so the result makes sense for everyone.&lt;/p&gt;

&lt;p&gt;This process of &lt;em&gt;transforming operations&lt;/em&gt; against each other is the key. It allows each client to apply changes locally and immediately, while still converging to the same final state.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Strengths&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mature and Proven&lt;/strong&gt;: OT has been used in production systems for decades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-latency UX&lt;/strong&gt;: Edits appear instantly (optimistic updates), with conflicts resolved on the backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient&lt;/strong&gt;: Operations are usually small, and the server controls ordering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Weaknesses&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard to Implement Correctly&lt;/strong&gt;: Writing the transformation functions (e.g., for text, trees, etc.) is non-trivial and error-prone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-Centric&lt;/strong&gt;: OT relies on a central authority to maintain order. It's not ideal for offline-first or peer-to-peer apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not Naturally Composable&lt;/strong&gt;: Composing OTs for rich content (like lists inside tables inside documents) becomes complex.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Real-World Usage&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Docs&lt;/strong&gt;: Arguably the most famous use case&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ShareDB&lt;/strong&gt;: An open-source OT-based backend for JSON/document editing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firepad&lt;/strong&gt;: Collaborative code/text editor built on Firebase and OT&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Example&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Suppose we have this initial text:&lt;/p&gt;

&lt;p&gt;"Hello"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User A inserts "X" at position 1 → insert(1, "X")&lt;/li&gt;
&lt;li&gt;User B deletes character at position 2 → delete(2)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HXello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hllo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without transformation, applying both ops in the wrong order could break the text.&lt;/p&gt;

&lt;p&gt;With OT, we transform each op against the other so both edits are preserved meaningfully.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhv15ufwl7m2y845ot4vm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhv15ufwl7m2y845ot4vm.png" alt="OT example" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CRDTs: Sync Later, Merge Automatically
&lt;/h2&gt;

&lt;p&gt;While OT was designed for centralized collaboration, CRDTs (Conflict-free Replicated Data Types) were born from the needs of distributed and offline-capable systems. If OT is all about ordering and coordination, CRDTs take the opposite approach: let every device do what it wants, and merge later, automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How CRDTs Work&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;CRDTs are special data structures that are mathematically designed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commutative (order doesn't matter)&lt;/li&gt;
&lt;li&gt;Associative (grouping doesn't matter)&lt;/li&gt;
&lt;li&gt;Idempotent (reapplying changes doesn't change the result)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apply changes in &lt;strong&gt;any order&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Synchronize &lt;strong&gt;without needing a central server&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Merge state from &lt;strong&gt;any number of devices;&lt;/strong&gt; even after long periods offline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two main types of CRDTs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Operation-based CRDTs&lt;/strong&gt;: Sync deltas (changes); smaller network load but require reliable delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State-based CRDTs&lt;/strong&gt;: Sync complete state periodically; simpler merge logic but heavier payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: If two people both insert characters at the same spot, a CRDT structure ensures both are preserved and deterministically ordered.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Strengths&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Perfect for Offline-First Apps&lt;/strong&gt;: Clients can work independently and sync later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Central Server Required&lt;/strong&gt;: Suitable for peer-to-peer and edge architectures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Conflict Resolution&lt;/strong&gt;: You don't need to write transform functions or deal with "merge hell."&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Weaknesses&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heavier Resource Usage&lt;/strong&gt;: May store tombstones, causal metadata, or complete histories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Data Structures&lt;/strong&gt;: Lists, nested objects, and rich text require advanced CRDT variants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less Mature Ecosystem&lt;/strong&gt;: Tooling is growing, but not as battle-tested as OT.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Real-World Usage&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automerge&lt;/strong&gt;: JSON-like CRDT library for JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yjs&lt;/strong&gt;: Highly optimized CRDT framework, widely used in collaborative editors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Figma&lt;/strong&gt;: Uses a &lt;a href="https://www.figma.com/blog/how-figmas-multiplayer-technology-works/" rel="noopener noreferrer"&gt;hybrid model&lt;/a&gt;, with CRDT-like properties for real-time sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion&lt;/strong&gt;: Uses CRDT-based techniques for syncing changes offline&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Example&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let's say the initial value is an empty list: &lt;code&gt;[]&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User A inserts "A" at position 0 (offline)&lt;/li&gt;
&lt;li&gt;User B inserts "B" at position 0 (offline)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="nc"&gt;A &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offline&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="nc"&gt;B &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offline&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// When both clients sync:&lt;/span&gt;
&lt;span class="nx"&gt;CRDT&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;// or ["B", "A"], based on internal IDs&lt;/span&gt;

&lt;span class="nc"&gt;Final &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consistent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="c1"&gt;// same across all devices&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When both sync:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The CRDT merges these operations into a consistent order (e.g., [A, B] or [B, A]) based on causal metadata&lt;/li&gt;
&lt;li&gt;No data is lost, and both clients reach the same result: &lt;strong&gt;no manual conflict resolution required&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5b2gwdyq20p67bhpppqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5b2gwdyq20p67bhpppqy.png" alt="CRDT example" width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Choosing the Right Model: OT or CRDT?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There's no universal winner between Operational Transforms and CRDTs, it all comes down to your product's &lt;strong&gt;needs, constraints, and future roadmap&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Below is a practical guide to help you choose the right approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Decision Criteria&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Do users need to collaborate offline?&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes&lt;/strong&gt; → &lt;strong&gt;CRDTs&lt;/strong&gt; are the better fit. They allow users to work offline and merge changes later without conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No&lt;/strong&gt; (always-online app) → &lt;strong&gt;OT&lt;/strong&gt; may be simpler to implement and more efficient.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. Is your system centralized or peer-to-peer?&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Centralized: &lt;strong&gt;OT&lt;/strong&gt; is optimized for client-server topologies.&lt;/li&gt;
&lt;li&gt;Decentralized or P2P: &lt;strong&gt;CRDTs&lt;/strong&gt; are built for this, with no need for a central authority.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;3. Is document fidelity critical (e.g., rich text, layout precision)?&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OT&lt;/strong&gt; works well for linear content (like text), but complex structures require a lot of custom work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRDTs&lt;/strong&gt; support nested and structured data (JSON, trees), but often need more memory and tuning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;4. How much control do you have over the client environment?&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Full control (e.g., internal tool or company-wide app): &lt;strong&gt;OT&lt;/strong&gt; might be fine, since you can enforce coordination rules.&lt;/li&gt;
&lt;li&gt;Public, multi-device, or mobile-heavy apps: &lt;strong&gt;CRDTs&lt;/strong&gt; handle edge cases like clock skew, unreliable networks, and user churn more gracefully.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;5. Is data merge correctness more important than sync speed?&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;For instant collaboration with low conflict rates: &lt;strong&gt;OT&lt;/strong&gt; can be faster.&lt;/li&gt;
&lt;li&gt;For resilience and correctness even under messy conditions: &lt;strong&gt;CRDTs&lt;/strong&gt; shine.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Important: these models aren't mutually exclusive. Some modern tools (like Notion or Figma) use &lt;strong&gt;hybrid approaches&lt;/strong&gt;: OT for performance-critical paths, CRDTs for background sync and recovery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Closing Thoughts: Choose the Right Tool for the Right Collaboration&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Real-time collaboration is no longer a niche feature, it's a baseline expectation in modern productivity tools. But building it is anything but trivial.&lt;/p&gt;

&lt;p&gt;Both Operational Transforms and Conflict-free Replicated Data Types offer robust solutions to the problem of concurrent edits, but they approach it from fundamentally different angles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OT&lt;/strong&gt; shines in environments where coordination is possible and performance is critical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRDTs&lt;/strong&gt; excel when flexibility, offline support, and distributed trust are essential.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As engineers, our job isn't to chase elegance or novelty, it's to ship systems that work for real people in the real world. Understanding the trade-offs between OT and CRDTs can help you build collaborative experiences that don't just sync; they scale, they recover, and they &lt;em&gt;feel&lt;/em&gt; right.&lt;/p&gt;

&lt;p&gt;Bottom line is: You don't just choose a sync algorithm; you choose a &lt;strong&gt;collaboration philosophy&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OT&lt;/strong&gt; is about control and coordination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRDTs&lt;/strong&gt; are about trust and eventual harmony.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either way, it's not just about the data structure; it's about how your users interact, when they connect, and what they expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Further Reading &amp;amp; Resources&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRDTs&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fission.codes/blog/crdts-for-mortals/" rel="noopener noreferrer"&gt;CRDTs for Mortals - Fission&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/automerge/automerge" rel="noopener noreferrer"&gt;Automerge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yjs/yjs" rel="noopener noreferrer"&gt;Yjs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/papers/ot.pdf" rel="noopener noreferrer"&gt;Martin Kleppmann - "A Critique of the Operational Transformation Approach"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;OT&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/share/sharedb" rel="noopener noreferrer"&gt;ShareDB (open-source OT backend)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neil.fraser.name/writing/ot/" rel="noopener noreferrer"&gt;Operational Transformation Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://patents.google.com/patent/US6289433B1/en" rel="noopener noreferrer"&gt;Google's OT patent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;General&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nutrient.io/guides/web/annotations/synchronization/" rel="noopener noreferrer"&gt;Synchronizing annotations across users, devices, and sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nutrient.io/demo/instant-collaboration?i=persistent_UCtb7lbOA2MeAFiJ5AYjF8E3xRHB0wob.vF1P-9e_Dp67lL_0UJpykw.5J5K" rel="noopener noreferrer"&gt;Instant Collaboration Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.inkandswitch.com/local-first/" rel="noopener noreferrer"&gt;Ink &amp;amp; Switch - Local-first Software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dataintensive.net/" rel="noopener noreferrer"&gt;Designing Data-Intensive Applications by Martin Kleppmann&lt;/a&gt; &lt;em&gt;(Ch. 5-9 dive into consistency models and CRDTs)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>programming</category>
    </item>
    <item>
      <title>Thinking Clearly with LLMs: Mental Models and Cognitive Pitfalls in Prompt Engineering</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Wed, 06 Aug 2025 16:38:57 +0000</pubDate>
      <link>https://forem.com/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm</link>
      <guid>https://forem.com/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm</guid>
      <description>&lt;p&gt;Large Language Models feel intelligent in conversation, but their behavior is fundamentally alien to human cognition. They don't think like we do, reason like we do, or understand context like we do. Yet we keep trying to interact with them as if they were human collaborators.&lt;/p&gt;

&lt;p&gt;This mismatch leads to fragile, confusing, and often overconfident prompt engineering. We write prompts that work sometimes, fail mysteriously, and leave us scratching our heads about what went wrong.&lt;/p&gt;

&lt;p&gt;The solution isn't better prompt templates or more sophisticated techniques. It's &lt;strong&gt;thinking more clearly&lt;/strong&gt; about what LLMs actually are and how they actually work.&lt;/p&gt;

&lt;p&gt;This post will walk through mental models that accurately describe how LLMs behave in practice, cognitive pitfalls that distort our reasoning when writing prompts, and practical frameworks for building more reliable AI systems.&lt;/p&gt;

&lt;p&gt;Whether you're building AI features into your products or just trying to understand these systems better, this guide will help you avoid the traps that lead to brittle, confusing, or overconfident prompt engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLMs Don't Think Like We Do
&lt;/h2&gt;

&lt;p&gt;The fundamental problem is that we anthropomorphize LLMs. We treat them like they have intentions, understanding, or agency. But they don't.&lt;/p&gt;

&lt;p&gt;An LLM is a statistical pattern matcher trained on vast amounts of text. It predicts the next token based on what came before, nothing more. Yet we keep trying to reason with it as if it were a human collaborator. This fundamental misunderstanding is what &lt;a href="https://dl.acm.org/doi/10.1145/3442188.3445922" rel="noopener noreferrer"&gt;Emily Bender and colleagues&lt;/a&gt; called the "stochastic parrot" problem in their influential paper.&lt;/p&gt;

&lt;p&gt;This mismatch creates fragile prompts that work in some contexts but fail in others, confusing interactions where the model seems to understand but then behaves unexpectedly, overconfidence in the model's capabilities, and inefficient development cycles spent debugging "unpredictable" behavior.&lt;/p&gt;

&lt;p&gt;The solution is to develop accurate mental models of how LLMs actually work and to recognize the cognitive pitfalls that lead us astray.&lt;/p&gt;

&lt;h2&gt;
  
  
  Durable Mental Models for Working with LLMs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Stochastic Parrot
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: LLMs are sophisticated pattern matchers, not reasoning engines.&lt;/p&gt;

&lt;p&gt;At their heart, LLMs predict the next token based on statistical patterns in their training data. They don't &lt;a href="https://arxiv.org/abs/1801.00631" rel="noopener noreferrer"&gt;"understand"&lt;/a&gt; concepts in the way humans do, but they recognize patterns and continue them.&lt;/p&gt;

&lt;p&gt;This means ambiguity is problematic because vague prompts can produce unpredictable results. Precision matters, with specific, well-structured prompts working better. Hallucination is inevitable, as models will confidently generate plausible but false information. And context is everything, as the model's behavior depends heavily on the immediate context.&lt;/p&gt;

&lt;p&gt;The practical takeaway? Write prompts that are explicit, specific, and leave little room for interpretation. Don't assume the model will "figure out" what you mean.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Simulator
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: LLMs can simulate roles, workflows, or behaviors based on the context you provide.&lt;/p&gt;

&lt;p&gt;When you write a prompt like "Act as a helpful assistant," the model doesn't become an assistant. It samples from patterns in its training data that match how helpful assistants tend to speak and behave. In effect, you're constructing a lightweight simulation that persists for the duration of the prompt.&lt;/p&gt;

&lt;p&gt;This is powerful. Specifying a role like developer, analyst, or teacher can shift the model's tone, structure, and output format. Roles help set expectations and improve consistency, especially across multiple turns. However, if you mix conflicting roles or instructions, the simulation can break down or become incoherent.&lt;/p&gt;

&lt;p&gt;The practical takeaway? Be intentional about the role you want the model to play. Specify the persona, goals, and constraints clearly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Conversational Mirror
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: LLMs echo your tone, ambiguity, and structure.&lt;/p&gt;

&lt;p&gt;The model's output quality and style directly reflects the quality and style of your input. If your prompt is vague, the response will be vague. If your prompt is precise, the response will be more precise.&lt;/p&gt;

&lt;p&gt;This means input quality maps directly to output quality, following the classic "garbage in, garbage out" principle. The model adopts your communication style through tone matching. Structure matters, with well-structured prompts producing well-structured responses. And each interaction builds on the previous ones for iterative improvement.&lt;/p&gt;

&lt;p&gt;The practical takeaway? Write prompts as if you're writing for a very smart but literal colleague. Be clear, specific, and well-structured.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Prompt is the Program
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: Prompting is declarative, dynamic programming with text as the universal interface.&lt;/p&gt;

&lt;p&gt;Your prompt is the "code" that runs on the LLM. It defines the inputs, outputs, constraints, and behavior. Like any program, it needs to be well-structured, modular, and maintainable. This aligns with the broader shift toward what Andrej Karpathy called &lt;a href="https://karpathy.medium.com/software-2-0-a64152b37c35" rel="noopener noreferrer"&gt;Software 2.0&lt;/a&gt;, where programs are defined not just by explicit logic, but by data, models, and context.&lt;/p&gt;

&lt;p&gt;Complex prompts often create layered, role-specific micro-worlds, with each layer having its own role, goals, and constraints. The model navigates these layers to produce appropriate responses. This layering can create sophisticated behavior, but role conflicts can arise when different layers have conflicting goals.&lt;/p&gt;

&lt;p&gt;Since most interaction with an LLM happens through text, the prompt acts like its user interface. And just like any UI, clarity, structure, and consistency go a long way. A clear, well-structured prompt makes the model easier to work with and more likely to respond reliably. When your prompts follow consistent patterns, you get more predictable outputs. Over time, you'll spot fedback loops, what works, what breaks, where ambiguity creeps in, and that helps you refine things. This is why modularity matters. Break complex prompts into smaller, reusable parts. Keep track of what you change and why, just like you would with code.&lt;/p&gt;

&lt;p&gt;The practical takeaway? Treat prompt engineering like software engineering. Use version control, test the model, document your approach, and design your prompts with the same care you'd use for a user interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cognitive Pitfalls in Prompt Engineering
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I've found knowing about the &lt;strong&gt;Einstellung Effect&lt;/strong&gt; and &lt;strong&gt;Type III Error&lt;/strong&gt;, in particular, instrumental in LLM prompting. Knowing about these two biases can help you a lot with structuring your prompts correctly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Type III error: Solving the Wrong Problem
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Framing mistakes lead to elegant failures.&lt;/p&gt;

&lt;p&gt;In statistical reasoning, most people are familiar with Type I errors (false positives) and Type II &lt;a href="https://en.wikipedia.org/wiki/Type_I_and_type_II_errors" rel="noopener noreferrer"&gt;errors&lt;/a&gt; (false negatives). Less well known, but just as important in prompt engineering, is the &lt;a href="https://en.wikipedia.org/wiki/Type_III_error" rel="noopener noreferrer"&gt;&lt;strong&gt;Type III error&lt;/strong&gt;&lt;/a&gt;: solving the wrong problem.&lt;/p&gt;

&lt;p&gt;This happens when a prompt is well-crafted and followed precisely by the model, but the output is irrelevant or unhelpful because the underlying task was misunderstood. The model does exactly what it was asked to do, but the prompt was aimed at the wrong goal. The failure is not in the execution, but in the framing.&lt;/p&gt;

&lt;p&gt;You might assume the model can reason when it is really just predicting. You might misread what the user needs, focus on the wrong aspect of the workflow, or design an elegant prompt that misses the point entirely. Sometimes, the entire interaction is shaped more by the capabilities of the LLM than by what the broader system or product actually requires.&lt;/p&gt;

&lt;p&gt;To avoid this pitfall, start with the actual problem rather than jumping to an solution. Clarify the task intent before writing any prompts. Test your assumptions about the problem at hand and what the model can actually do. Before writing a single token, step back and define the problem clearly. Only then should you start designing the prompt.&lt;/p&gt;

&lt;p&gt;Start with the problem. Then design the prompt. Not the other way around.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Einstellung Effect: Prompt Fixation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Reuse bias leads to suboptimal prompts.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Einstellung_effect" rel="noopener noreferrer"&gt;Einstellung effect&lt;/a&gt; is a cognitive bias where a familiar solution blocks recognition of a better one. In the context of prompt engineering, this often appears when a previously successful prompt becomes a default, even when it no longer fits the task.&lt;/p&gt;

&lt;p&gt;Common mistakes include reusing the same prompt structure for different problems, not experimenting with different approaches, getting stuck in familiar patterns, or ignoring evidence that suggests a different approach.&lt;/p&gt;

&lt;p&gt;To avoid this pitfall, stay flexible and iterative. Experiment with different prompt structures. Question your assumptions regularly. And look for evidence that suggests different approaches might work better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Overfitting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: A prompt that works on one example might fail everywhere else.&lt;/p&gt;

&lt;p&gt;Prompt overfitting happens when you design and test a prompt using only one or two inputs, then assume it will behave the same way in general. It feels like success, but it's often an illusion. The prompt may have worked because the input was easy, familiar, or unintentionally aligned with the model's defaults. Once the input shifts, longer content, edge cases, different tone, the output breaks down or drifts unpredictably.&lt;/p&gt;

&lt;p&gt;This is especially risky when you're working with limited test data or optimizing prompts by trial and error on a small set of examples. You end up with something that looks robust, but actually performs well only in narrow conditions.&lt;/p&gt;

&lt;p&gt;To avoid this, evaluate prompts across a wide range of realistic inputs. Include examples that vary in structure, tone, length, or ambiguity. Treat your prompt like a function, it should be predictable, consistent, and stable under real-world conditions, not just the happy path.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fluency Illusion
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Coherent ≠ correct.&lt;/p&gt;

&lt;p&gt;LLMs are excellent at producing coherent, plausible-sounding text. But coherence doesn't guarantee accuracy, especially for factual or logical tasks. This is what &lt;a href="https://arxiv.org/abs/2207.05221" rel="noopener noreferrer"&gt;researchers call the "fluency illusion"&lt;/a&gt;, where models generate convincing but incorrect information.&lt;/p&gt;

&lt;p&gt;Common mistakes include trusting the model's confidence, not fact-checking important information, assuming coherence means correctness, or not verifying logical consistency.&lt;/p&gt;

&lt;p&gt;To avoid this pitfall, always verify important factual claims. Check for logical consistency. Don't trust the model's confidence level. And use the model for what it's good at (generation) while verifying separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anthropomorphic Drift
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Attributing agency where there is none.&lt;/p&gt;

&lt;p&gt;It's easy to start thinking of the LLM as having intentions, understanding, or agency. When it comes to LLMs and AI, it's a particularly dangerous cognitive trap because the outputs sound human, even though they result from statistical inference. This is a natural response to fluent language, but it leads to overtrusting the model's output or falsely assuming it has reasoning abilities. This &lt;a href="https://www.researchgate.net/publication/340320918_Anthropomorphism_in_AI" rel="noopener noreferrer"&gt;anthropomorphic bias&lt;/a&gt; is well-documented in human-AI interaction research.&lt;/p&gt;

&lt;p&gt;Common mistakes include thinking the model "understands" your intent, attributing reasoning to statistical pattern matching, trusting the model's "judgment," or treating the model like a human collaborator.&lt;/p&gt;

&lt;p&gt;To avoid this pitfall, remember that the model is a statistical pattern matcher. Don't attribute understanding or agency. Verify important outputs independently. And keep the model's limitations in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompting as Debugging
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Not treating prompt engineering as an experimental, iterative process.&lt;/p&gt;

&lt;p&gt;Prompt engineering is more like debugging than traditional programming. It requires hypothesizing, testing, and refining based on results. This &lt;a href="https://arxiv.org/abs/2303.07839" rel="noopener noreferrer"&gt;iterative approach&lt;/a&gt; is essential for building reliable AI systems.&lt;/p&gt;

&lt;p&gt;Common mistakes include expecting prompts to work perfectly on the first try, not iterating based on results, not isolating variables when testing, or giving up too quickly when things don't work.&lt;/p&gt;

&lt;p&gt;To avoid this pitfall, treat prompting as iterative and experimental. Hypothesize, isolate, and refine systematically. Test one change at a time. And learn from failures and unexpected results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Mental Clarity Beats Prompt Tinkering
&lt;/h2&gt;

&lt;p&gt;The most important skill in working with LLMs isn't knowing the latest prompt techniques or having the most sophisticated templates. It's &lt;strong&gt;thinking clearly&lt;/strong&gt; about what these systems actually are and how they actually work.&lt;/p&gt;

&lt;p&gt;When you understand that LLMs are statistical pattern matchers, not reasoning engines, you write different prompts. When you recognize that coherence doesn't equal correctness, you build different systems. When you see prompting as declarative programming, you approach it with different tools and practices.&lt;/p&gt;

&lt;p&gt;The clearer you think about LLMs, the more reliable your outcomes will be. Mental models help you reason about these systems. Prompts are interfaces, not dialogues. And the better you understand the cognitive pitfalls that distort your thinking, the more effectively you can avoid them.&lt;/p&gt;

&lt;p&gt;The future of AI development isn't about writing better prompts; it's about thinking more clearly about what we're actually building and how these systems actually work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I've read... The Pragmatic Programmer</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Sun, 16 Jan 2022 18:24:51 +0000</pubDate>
      <link>https://forem.com/puritanic/ive-read-the-pragmatic-programmer-2bn9</link>
      <guid>https://forem.com/puritanic/ive-read-the-pragmatic-programmer-2bn9</guid>
      <description>&lt;p&gt;Truly a classic. It's definitely a must-read book for programmers and even people managing programmers. Initially released in 1999, &lt;a href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/" rel="noopener noreferrer"&gt;The Pragmatic Programmer&lt;/a&gt; is a book about becoming a Pragmatic Programmer - programmer that's a true professional in their craft. And, even though it was published twenty years ago, it's fascinating to see the struggles we still face day in and day out discussed even then.&lt;/p&gt;

&lt;p&gt;When I first started reading this book, I've expected a lot of technical details and lessons, which is probably one of the reasons why I've been avoiding this book so far. I mean, it's &lt;strong&gt;twenty&lt;/strong&gt; years old, and in today's pace of technology, technical details do not stay up to date very long. But instead, this book concerns the most challenging parts of the programmer's career: writing scalable and maintainable software and scaling themselves as professionals.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;There are code snippets&lt;/em&gt;, but the authors are aware of the code and techniques getting out of date in a matter of years, so the book is not focusing on them too much.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In general, there are not many surprises in what the book's authors are trying to deliver. Any programmer who cares about their craft, has no fear of change, and already has a few years of experience will already know many themes explored in this book. In my opinion, most programmers are aware of the guidelines this book is preaching, but they are also quick on finding excuses to ignore them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Pragmatic Programmer&lt;/em&gt; centers on how to use software to solve problems effectively and how to grow as the developer pragmatically; not just how to be a good programmer, but also how to solve the complex issues that surround coding, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Writing clean code through &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY&lt;/a&gt; (Don't repeat yourself) and &lt;a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it" rel="noopener noreferrer"&gt;YAGNI&lt;/a&gt; (You aren't gonna need it)&lt;/li&gt;
&lt;li&gt;  How to estimate the software delivery.&lt;/li&gt;
&lt;li&gt;  How to institute change when others are hesitant.&lt;/li&gt;
&lt;li&gt;  How to combat stagnancy as a developer.&lt;/li&gt;
&lt;li&gt;  How to make the software processes resilient and efficient through automation and testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The examples and explanations are not abstract or far-fetched but are somewhat real-world applications of things you could see in the industry (though some stuff is outdated).&lt;/p&gt;

&lt;p&gt;Some of the significant points of the book that I'm going to go through are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  We should take &lt;strong&gt;responsibility&lt;/strong&gt; for our code and decisions.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Do not leave broken windows unrepaired.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Think critically&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Know your tools&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Program and refactor deliberately&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Use Version Control&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Test your code&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Automate all the things&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hunt and Thomas are stating that the last three things from the list above are the essence of the Pragmatic Starter Kit, and that they should be the three legs that support every project.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Responsibility&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When you have responsibility for something, you should prepare yourself to be held accountable. If you make mistakes and cannot fulfil those responsibilities, you have to make up for them and find a solution. Don't give excuses and play the &lt;a href="https://en.wikipedia.org/wiki/Cover_your_ass" rel="noopener noreferrer"&gt;finger-pointing game&lt;/a&gt;. When you make a mistake (to err is human) or an error in judgment, admit it honestly and try to offer alternatives. Don't blame all the problems on a vendor, a programming language, management, or your coworkers. Any of these may play a role, but it is up to you to provide solutions, not excuses.&lt;/p&gt;

&lt;p&gt;Don't approach anyone to tell them that something couldn't be done before you are entirely sure that that's correct. Additionally, don't just say you can't do it, like it's the end of the story. Instead, provide options and explain what &lt;em&gt;can&lt;/em&gt; be done to salvage the situation.&lt;/p&gt;

&lt;p&gt;As the authors of the book say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Try to flush out the lame excuses before voicing them aloud. Does your excuse sound reasonable or stupid? How's it going to sound to your boss? (...) If you must, tell your cat first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Broken windows
&lt;/h2&gt;

&lt;p&gt;There is a story in the book, about research studying the &lt;a href="https://www.simplypsychology.org/broken-windows-theory.html" rel="noopener noreferrer"&gt;effect of broken windows&lt;/a&gt; on urban areas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One broken window, left unrepaired for any substantial length of time, instills in the inhabitants of the building a sense of abandonment—a sense that the powers that be don't care about the building. So another window gets broken. People start littering. Graffiti appears. Serious structural damage begins. In a relatively short span of time, the building becomes damaged beyond the owner's desire to fix it, and the sense of abandonment becomes a reality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We, programmers, have probably seen this in some codebases. We see some broken code and think it's okay to leave it. We'll just come back when there is not enough work and fix it. But, unfortunately, this is just the first step to degradation of code quality and serious tech debt. After one broken window, the others will start appearing more frequently, and this is usually the time when developers start looking for a new job, leaving a dumpster fire behind.&lt;/p&gt;

&lt;p&gt;So, (please), &lt;strong&gt;don't leave "broken windows" (bad designs, wrong decisions, or poor code) unrepaired&lt;/strong&gt;. Fix each one as soon as it is discovered. If there is insufficient time to fix it properly, create a ticket, fix the most offending issue if possible, comment out the code, or leave a screaming comment. Ultimately, you should take action to prevent further damage and show that you're on top of the situation.&lt;/p&gt;

&lt;p&gt;Do not fall to &lt;a href="https://en.wikipedia.org/wiki/Bystander_effect" rel="noopener noreferrer"&gt;the Bystander effect&lt;/a&gt;, hoping that other developers will fix the problems. Instead, take the initiative, and be a catalyst for change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think critically
&lt;/h2&gt;

&lt;p&gt;You should think critically about what you read and hear. You need to ensure that your knowledge and opinions are unswayed by either vendor or media hype. Beware of the salesman who insists that their solution provides the only answer; it may or may not apply to you and your project, or they might be just trying to sell you the snake oil.&lt;/p&gt;

&lt;p&gt;Try to ask and think with the following questions when you want to get to the bottom of something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Ask the "Five Whys"&lt;/strong&gt; - Ask a question, and get an answer. Then, dig deeper by asking another "why?" Then, repeat the question as long as it's reasonable to do. You might be able to get closer to a root cause this way.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Who does this benefit?&lt;/strong&gt; - It may sound cynical, but following the money can be a helpful path to analyze.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;What's the context?&lt;/strong&gt; - Everything occurs in its context, which is why "one size fits all" solutions often don't work. Good questions to consider are "best for who?" What are the prerequisites, what are the consequences, short and long term?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;When or Where would this work?&lt;/strong&gt; - Under what circumstances? Don't stop with first-order thinking (what will happen next), but use second-order thinking: what will happen after that?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Why is this a problem?&lt;/strong&gt; - Is there an underlying model? How does the underlying model work? Do you even have the same problem?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Know your tools
&lt;/h2&gt;

&lt;p&gt;This could seem like simple advice, but we are surrounded by a wide range of tools in our daily jobs. I don't know the ins and outs of each one I'm using, for sure.&lt;/p&gt;

&lt;p&gt;For example, a few days ago, I've learned about &lt;a href="https://johnkary.net/blog/git-add-p-the-most-powerful-git-feature-youre-not-using-yet/" rel="noopener noreferrer"&gt;&lt;em&gt;git add --patch&lt;/em&gt;&lt;/a&gt; functionality that allows us to stage only parts of the changed files.&lt;/p&gt;

&lt;p&gt;While I'm not sure that it would be advisable to learn everything possible about tools we're using for development, learning about stuff that can make you productive is definitely something you should strive for. For example, pay attention to your daily flow and see what manual actions you are performing most often. Then look if those can be automated or improved somehow.&lt;/p&gt;

&lt;p&gt;The book teaches us that it's essential to find the proper tools before starting the development. By essential tools, it's not just the IDE but also the programming language and services. The more you are versed with different technologies, the wider the picture you'll have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marcel.is/quench/" rel="noopener noreferrer"&gt;So before blindly jumping into coding, take a step back&lt;/a&gt;. Understand why the problem or the feature at hand needs to be built in the first place. Next, find the right tools for the job and start coding.&lt;/p&gt;

&lt;p&gt;Some of the authors' recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Use plain text for everything. Avoid using binary formats to keep knowledge (such as MS Word).&lt;/li&gt;
&lt;li&gt;  Learn some scripting language well to use it for text manipulation (Js, Ruby, Python).&lt;/li&gt;
&lt;li&gt;  Learn shell (awk, grep, etc.)&lt;/li&gt;
&lt;li&gt;  Have your &lt;a href="https://github.com/Puritanic/.dotfiles_new" rel="noopener noreferrer"&gt;dotfiles&lt;/a&gt; configured and backup them regularly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Program and refactor deliberately
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the face of ambiguity, refuse the temptation to guess.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Be wary of premature optimization. It's always a good idea to make sure an algorithm is a bottleneck before investing your precious time trying to improve it. The less code is there, the fewer chances for bugs to happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always be aware of what you are doing&lt;/strong&gt; - if you don't understand the background of the feature you're implementing, you may fall victim to false assumptions. Don't blindly copy/paste code you don't understand, and don't do shotgun programming or programming by coincidence, as the book's authors call it. For example, suppose you don't know why or how your program works. In that case, you'll probably end up in a situation where you don't understand why the code is failing, which would usually result in spending a significant amount of time chasing the piece of code until you (if) know how it was working in the first place.&lt;/p&gt;

&lt;p&gt;A litmus test for the above - can you explain your code to a more junior programmer? If not, perhaps you are relying on coincidences.&lt;/p&gt;

&lt;p&gt;As mentioned in the previous section, don't jump to coding right away. Instead, &lt;strong&gt;create a plan&lt;/strong&gt;, even if it's just a to-do list written as comments in your editor or on a napkin. After that, prioritize your efforts by spending time first on the complex parts of the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't be a slave to history,&lt;/strong&gt; meaning that you should not let existing code dictate future code. All code can be replaced if it is no longer appropriate. Even within one program, don't let what you've already done constrain what you do next, be ready to refactor, but keep in mind that this decision may impact the project schedule. The assumption here is that the impact of doing refactor will be less than the cost of not making the change.&lt;/p&gt;

&lt;p&gt;Martin Fowler &lt;a href="https://www.martinfowler.com/bliki/DefinitionOfRefactoring.html" rel="noopener noreferrer"&gt;defines refactoring as&lt;/a&gt; a:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The critical parts of this definition are that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It is the process of restructuring code to make it easier to use, cleaner to work with, and removing &lt;a href="https://en.wikipedia.org/wiki/Code_smell" rel="noopener noreferrer"&gt;Code smells&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;External behavior does not change; this is not the time to add features&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To guarantee that the external behavior hasn't changed, you need good (preferably) automated unit testing that validates the code's behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Version Control
&lt;/h2&gt;

&lt;p&gt;This advice sounds a bit outdated, but it's worth a mention. Today's software development landscape is very different from twenty years ago, at least when version control is considered. &lt;code&gt;git&lt;/code&gt; is deeply entrenched in the development flow, and I, myself, can't imagine working on a project without version control.&lt;/p&gt;

&lt;p&gt;Additionally, the book suggests using version control for everything we deem important, notes and documentation as an example.&lt;/p&gt;

&lt;p&gt;My take on this is that besides the version control, you should also strive to keep your git history clean and searchable (by using semantic commits, for example) and utilize the VCS for automation whenever possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test your code
&lt;/h2&gt;

&lt;p&gt;Yet another common-sense advice. And still, one that's not utilized as much as it should be, in most cases. Testing was important twenty years ago, but today it's even more critical when we account for a growing number of programs that can &lt;a href="https://en.wikipedia.org/wiki/Therac-25" rel="noopener noreferrer"&gt;quickly&lt;/a&gt; &lt;a href="https://en.wikipedia.org/wiki/Multidata_Systems_International#accident" rel="noopener noreferrer"&gt;kill&lt;/a&gt; &lt;a href="https://en.wikipedia.org/wiki/Boeing_737_MAX_groundings" rel="noopener noreferrer"&gt;people&lt;/a&gt; in case of malfunction.&lt;/p&gt;

&lt;p&gt;Book authors even suggest that the test code should be larger than the program source code and that we should treat the test code with the same care as any production code. Keep it decoupled, clean, and robust. Don't rely on unreliable things like the absolute position of pages in a GUI system, exact timestamps in a server log, or the exact wording of error messages. Testing for these sorts of things will result in fragile (flaky) tests.&lt;/p&gt;

&lt;p&gt;The pragmatic programmer is ruthlessly testing their code. The time it takes to write test code is worth the effort, as it ends up being much cheaper in the long run, with a chance of producing a product with close to zero defects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate all the things
&lt;/h2&gt;

&lt;p&gt;Automation is the core principle of being a pragmatic programmer. You should find whatever manual tasks you've or someone on your team has been doing and automate them. Automation leaves less space for human error and drastically improves the processes.&lt;/p&gt;

&lt;p&gt;Automation also plays nicely with the other two "legs" of the pragmatic starter kit, version control, and tests. You should automate your processes to run tests on VSC changes and, if stable enough, even deploy the code to production on demand.&lt;/p&gt;

&lt;p&gt;The more stuff you automate, the more time you'll have to dedicate to the real problems. But (!), don't fall into &lt;a href="https://xkcd.com/1205/" rel="noopener noreferrer"&gt;the trap of automating something that's not really worth the effort&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0lkfya38rrykgj3qmos.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0lkfya38rrykgj3qmos.png" alt="obligatory XKCD" width="571" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pragmatic Teams
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Great things in business are never done by one person. They're done by a team of people."&lt;br&gt;
&lt;cite&gt;-- Steve Jobs&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Teams as a whole should not tolerate broken windows—those slight imperfections that no one fixes. Instead, the team must take responsibility for the quality of the product.&lt;/p&gt;

&lt;p&gt;As a final note, the book says that we, as individuals, should take pride in our work and leave our mark on it. However, we still need to balance this out when working in teams to not become prejudiced in favor of our code and against our coworkers. You shouldn't jealously defend your code against intruders, and you should treat other people's code with respect. The Golden Rule ("Do unto others as you would have them do unto you") and a foundation of mutual respect among the developers are critical to make this work.&lt;/p&gt;

&lt;p&gt;Anonymity can provide a breeding ground for sloppiness, mistakes, sloth, and bad code, especially on large projects. It becomes too easy to see yourself as just a cog in the wheel, producing lame excuses in endless status reports instead of good code.&lt;/p&gt;

&lt;p&gt;While code must be owned, it doesn't have to be owned by an individual. In fact, Kent Beck's eXtreme Programming recommends &lt;a href="http://www.extremeprogramming.org/rules/collective.html" rel="noopener noreferrer"&gt;collective ownership of code&lt;/a&gt; (but this also requires additional practices, such as pair programming, to guard against the dangers of anonymity).&lt;/p&gt;

&lt;p&gt;In the end, what you want of your career as a pragmatic programmer is for other people to recognize your signature. They see the feature or program built by you and expect it to be solid, well-written, tested, and documented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I suggest this book to everyone; it's an easy and interesting read, even though senior developers are less likely to learn something new from it. Also, this is just a quick and short(&lt;em&gt;ish&lt;/em&gt;) overview of the book's content, I haven't covered everything I've learned from it, and there is a lot of stuff that's also worth reading about, such as Orthogonality, Design by Contract, debugging, Tracer Bullets technique, and more.&lt;/p&gt;

</description>
      <category>books</category>
      <category>programming</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>I've read... A Mathematician's Lament</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Sun, 16 Jan 2022 18:24:41 +0000</pubDate>
      <link>https://forem.com/puritanic/ive-read-a-mathematicians-lament-33ol</link>
      <guid>https://forem.com/puritanic/ive-read-a-mathematicians-lament-33ol</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"If you want to build a boat, don't order people to gather wood and assign tasks and work, but instead teach people to long for the boundless immensity of the sea."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Mathematician's Lament is more of an extended essay than a book - one man's problems with mathematics education without a viable solution. While I agree with him that the current state of mathematics in schools and universities is a travesty, the kind of "solution" he recommends is not the real solution. We all know what most of us thought about the math (and many other fields) in ES and HS, and leaving it all to the student is not an excellent solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Mathematics is the art of explanation. If you deny students the opportunity to engage in this activity - to pose their own problems, to make their own conjectures and discoveries, to be wrong, to be creatively frustrated, to have an inspiration, and to cobble together their own explanations and proofs - you deny them mathematics itself."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think he argues that mathematics education gets tripped up in unnecessary formalism and syntax before conceptually interesting problems are tackled. The whole thing is defended with the ridiculous "you might need this someday" pragmatism that children will instantly tune out - is a sound one. It would be great if every elementary school teacher were the kind of engaged leader capable of putting their students to work on an exciting geometry or abstract algebra problem and wandering around not to give answers but to provide the occasional hint. But, unfortunately, I don't see how this will provide anyone with well-rounded mathematics education.&lt;/p&gt;




&lt;p&gt;Don't get me wrong! I liked the premise of his essay. I would love that my professors had just a pinch of Lockhart's teaching spirit; it would make studying math much enjoyable. But, while I can see how it would make learning math so much more fun, it isn't practical. I think it could be accomplished in a small setting, but let's face it, the world doesn't allow for exploration. You have to master what they want you to master, troubling as that may be.&lt;/p&gt;

</description>
      <category>books</category>
      <category>math</category>
    </item>
    <item>
      <title>Improving your (Web) Dev Foo</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Sat, 01 Feb 2020 20:28:36 +0000</pubDate>
      <link>https://forem.com/puritanic/improving-your-web-dev-foo-1m87</link>
      <guid>https://forem.com/puritanic/improving-your-web-dev-foo-1m87</guid>
      <description>&lt;p&gt;I've been writing this since the last year, and in the end, I wasn't sure if I should publish it as this is mostly just a rant. Hopefully, some of you might find something interesting here as I wrote up some of the stuff I've learned and I'm doing in practice to keep writing effective and clean code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor/IDE
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are starting learning about web development, you can't go wrong, pick any code editor and code on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Currently, for web development, there are many choices when you come to choosing the editor for your work. I use Webstorm/Xcode combo for work and VS Code/Vim for my stuff. From my experience, my suggestion would be that beginner devs start learning with a simple editor without many plugins, such as VS Code, Notepad ++ or Sublime Text, typing out all those keywords and language methods by hand helps a lot with memorizing/learning all that stuff. Once you feel comfortable with a language you're writing your code with, you can switch to full-blown IDE like Webstorm or plugins enhanced VS Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linters &amp;amp; Formatters
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;TLDR: Use Eslint and Prettier&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When your code base gets bigger, it's also more challenging to keep an eye on all those lines, and syntax errors problems creep in. By highlighting problematic code, undeclared variables, or missing imports, your productivity can be increased a lot and is going to save a lot of time and many nerves as well.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;Eslint&lt;/a&gt; from a very start would also help a lot with learning Js, as it will force you to build healthy habits and write clean code. Over the years, I've tailored my version of the eslint rules based on &lt;a href="https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb" rel="noopener noreferrer"&gt;eslint-config-airbnb&lt;/a&gt;, but lately, I've been looking into &lt;a href="https://github.com/wesbos/eslint-config-wesbos" rel="noopener noreferrer"&gt;Wes Bos's eslint config&lt;/a&gt; and would probably give it a go in a some of my future projects.&lt;/p&gt;

&lt;p&gt;Beside Eslint I'm using &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; for code formatting, combined with &lt;a href="https://github.com/typicode/husky" rel="noopener noreferrer"&gt;husky&lt;/a&gt; and &lt;a href="https://github.com/okonet/lint-staged" rel="noopener noreferrer"&gt;lint-staged&lt;/a&gt; for automating linting/formatting as precommit hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The optimal directory structure
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Dan Abramov's &lt;a href="http://react-file-structure.surge.sh/" rel="noopener noreferrer"&gt;solution&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have mixed feelings about this topic. The only thing that I'm sure of is that there is no right solution.&lt;br&gt;
Every application is different in some way or another, and each project has its own distinct needs. How we structure our applications should change based on the needs of the project, just like the technologies we choose.&lt;/p&gt;

&lt;p&gt;Do not try to optimize the project structure from the beginning of the project, but keep in mind that changing the project structure later in the project can be a problem in VCS because of history rewriting.&lt;/p&gt;

&lt;p&gt;That being said, &lt;em&gt;don't overthink it&lt;/em&gt;. Pick a folder structure that works for your application. If you’re spending a massive amount of time organizing and reorganizing components, containers, styles, reducers, sagas, you’re doing it wrong.&lt;/p&gt;


&lt;h3&gt;
  
  
  File naming
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Rant incoming&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Regarding file naming, one rule that I find immensely useful is this: &lt;em&gt;name your file the same as the thing you’re exporting from that file&lt;/em&gt;. I can't stress enough how angry I feel when I have hundreds on index.js files in a poorly structured project, and finding some chunk of logic takes so much time, which feels wasted...&lt;/p&gt;

&lt;p&gt;It staggers me that some people are happy to work like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w42c6r4vslouu3eagqu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w42c6r4vslouu3eagqu.png" alt="indexes everywhere" width="562" height="26"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even if your IDE is clever and puts the directory in the tab name for non-unique filenames, you still have a bunch of redundancy there, and will run out of tab room sooner, and you still can’t type the filename to open it. Having said that, I understand that there is the reasoning behind this — It’s a definite trade-off. Shorter import statements vs. file names that match exports. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not a worthy trade-off, in my opinion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my case, for the last two or three years, I'm mostly using CRA's project structure, with a few modifications, like adding a &lt;code&gt;redux/&lt;/code&gt; or &lt;code&gt;sagas/&lt;/code&gt; dir for state management logic and moving all &lt;code&gt;jsx/tsx&lt;/code&gt; files to &lt;code&gt;components/&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Just &lt;code&gt;console.log&lt;/code&gt; anywhere where you think there is a code "smell."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Writing about debugging can be a post on its own, yet there is a lot of already excellent &lt;a href="https://developers.google.com/web/tools/chrome-devtools/javascript/" rel="noopener noreferrer"&gt;posts&lt;/a&gt; and &lt;a href="https://frontendmasters.com/courses/debugging-javascript/" rel="noopener noreferrer"&gt;courses&lt;/a&gt; on Js debugging so I'll keep it short.&lt;/p&gt;

&lt;p&gt;Many devs would say that using debugger looks more professional, but the &lt;code&gt;console.log&lt;/code&gt; is something I'm using the most for a quick debugging. I'm lately working on the apps for Smart TVs and streaming devices, and those are not really debugger friendly, so logging data in console or going through device logs in &lt;code&gt;telnet&lt;/code&gt; is sometimes the only way to debug. That aside, you should learn how to use the debugger, as it can save you with more complex bugs.&lt;/p&gt;

&lt;p&gt;The simplest way to debug, at least in terms of tooling, is by reading the code you wrote. After that, use the &lt;code&gt;console.log&lt;/code&gt;s, and if even that doesn't pinpoint the problem, switch to the debugger and good luck.&lt;/p&gt;
&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't write a comment just for the sake of it. Write comments only for stuff that needs explanation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We all (hopefully) know how important proper documentation and reference material is to a successful software project. Without good docs, a particular library may be next to impossible to use. Without a reference to how different components and methods work in isolation, let alone examples of how all the different pieces of a project fit together with each other, we are left to interpret the original intention of the author merely by reading the source code, or if we are lucky, reaching for StackOverflow and googling random error messages. If this is an in-house or small project, you are probably entirely screwed (been there).&lt;/p&gt;

&lt;p&gt;This is especially important if you are working with several other fellow devs on the project; think about what the other member of the team is going to think about that hack you wrote when he doesn't know why is that needed. By keeping knowledge of how the code works and why is there many hacks in it or intentionally making code more complicated than it needs to be, you are just making lives of the everyone working on the same project more harder. And if you are doing this for the sole purpose of assuring your job security, you are just a &lt;strong&gt;&lt;code&gt;censored&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For documenting my projects, I've been using &lt;a href="https://jsdoc.app/" rel="noopener noreferrer"&gt;JSDoc&lt;/a&gt; syntax. JSDoc is a standardized way of writing comments in your code to describe functions, classes, methods, and variables in your codebase. The idea is that we describe how our code works with a few special keywords and formatting conventions, and later we can use a parser to run through all of our commented code and generate beautiful, readable documentation based on the comments we write.&lt;/p&gt;

&lt;p&gt;Here’s a short example of how's it looking like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @desc Represents a book.
 * @param {string} title - The title of the book.
 * @param {string} author - The author of the book.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of this stuff can be replaced with Typescript types, but even with that, for more complex functions, it's helpful if we have a good explanation of what it is doing and why do we need it to do that.&lt;/p&gt;

&lt;p&gt;Also, &lt;em&gt;every and one hack in your code should be documented&lt;/em&gt;, believe me, future you are going to be thankful for that.&lt;/p&gt;

&lt;p&gt;And for the end, if you already haven't, please read &lt;a href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882" rel="noopener noreferrer"&gt;Clean-Code&lt;/a&gt; by Robert C. Martin. Writing clean and readable code is a skill on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn Javascript
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;But seriously&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jumping on a Js framework or using a &lt;a href="https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore" rel="noopener noreferrer"&gt;library&lt;/a&gt; to solve problems you have because you're not familiar with the core language is going to bite you soon enough. But do not despair, most of us have been there on some level, Js documentation is enormous and, unless you have an excellent memory, there is no way to memorize even a quarter of this &lt;a href="https://www.ecma-international.org/ecma-262/10.0/index.html#Title" rel="noopener noreferrer"&gt;stuff&lt;/a&gt;. But leveraging &lt;a href="https://en.wikipedia.org/wiki/Pareto_principle" rel="noopener noreferrer"&gt;Pareto principle&lt;/a&gt; (also known as the 80/20 rule) would be in many cases just enough. Learn how is &lt;code&gt;this&lt;/code&gt; working, all possible operations you can do with objects and arrays, that in Js everything is an object, scope rules, async ops, prototypes (you'll rarely use these, but it's essential to understand how inheritance in Js works) and coercion scenarios (so you can laugh at people making stupid mistakes by adding numbers to strings or arrays to arrays and then creating posts on Reddit flaming Js).&lt;/p&gt;

&lt;p&gt;There is truth in saying that if you know Javascript good, then all frameworks and tools based on it are going to be much easier to learn. In the end, those are all just Js under the hood.&lt;/p&gt;

&lt;p&gt;I can also recommend reading &lt;a href="https://github.com/getify/You-Dont-Know-JS" rel="noopener noreferrer"&gt;You Don't Know JS&lt;/a&gt; book series if you want to dive deep into Js core mechanisms. (I'm rereading it for the 2nd time).&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the latest standards
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep up with times&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can be challenging to keep up with all the things going on in the web development world, especially since the JavaScript language itself has been changing a lot over the last several years. In 2015, TC39, the committee responsible for drafting the ECMAScript specifications decided to move to a yearly model for defining new standards, where new features would be added as they were approved, rather than drafting complete planned out specs that would only be finalized when all features were ready. As a result, we have ES6 - ES10 specifications which have changed the language a lot, and in a better way. Each of these specifications includes a set of new features/improvements integrated into Javascript, nullifying the need for cumbersome libraries or tools so that you can work with Js and not pull your hair out.&lt;/p&gt;

&lt;p&gt;If you want to get a quick overview of the features being considered for future releases, the best place to look is the &lt;a href="https://github.com/tc39/proposals" rel="noopener noreferrer"&gt;TC39 proposals repo on Github&lt;/a&gt;. Proposals go through a 4 stage process, where stage 1 can best be understood as a cool “idea,” and stage 4 is “confirmed for the next ECMAScript release.” There is a lot of &lt;a href="https://github.com/tc39/proposals/blob/master/finished-proposals.md" rel="noopener noreferrer"&gt;cool stuff&lt;/a&gt; coming with ES10 :)&lt;/p&gt;

&lt;p&gt;If you’re interested in keeping up with new proposals but want somebody to walk you through them, I recommend subscribing to Axel Rauschmayer’s &lt;a href="https://2ality.com/" rel="noopener noreferrer"&gt;2ality&lt;/a&gt; blog. But if you are more of a social interaction person, probably the easiest way to keep up with language evolution is to follow the people who are shaping and teaching the new language features: &lt;a href="https://twitter.com/TC39" rel="noopener noreferrer"&gt;@TC39&lt;/a&gt;, &lt;a href="https://twitter.com/sebmarkbage" rel="noopener noreferrer"&gt;Sebastian Markbåge&lt;/a&gt;, &lt;a href="https://twitter.com/mathias" rel="noopener noreferrer"&gt;Mathias Bynens&lt;/a&gt;, &lt;a href="https://twitter.com/littledan" rel="noopener noreferrer"&gt;Daniel Ehrenberg&lt;/a&gt;, &lt;a href="https://twitter.com/bitandbang" rel="noopener noreferrer"&gt;Tierney Cyren&lt;/a&gt;, &lt;a href="https://twitter.com/rauschma" rel="noopener noreferrer"&gt;Axel Rauschmayer&lt;/a&gt; and probably a lot of other people I forgot.&lt;/p&gt;

&lt;p&gt;Babel has implemented almost all of the higher stage proposals on the TC39 list, and you can try them out in the &lt;a href="https://babeljs.io/repl/#?babili=false&amp;amp;evaluate=true&amp;amp;lineWrap=false&amp;amp;presets=es2015%2Creact%2Cstage-2&amp;amp;targets=&amp;amp;browsers=&amp;amp;builtIns=false&amp;amp;debug=false&amp;amp;code_lz=Q" rel="noopener noreferrer"&gt;Babel REPL&lt;/a&gt; or by setting up a small project that loads in Babel with the appropriate plugins installed. Also, ff you aren’t familiar with ES6 yet, Babel has a &lt;a href="https://babeljs.io/docs/en/learn" rel="noopener noreferrer"&gt;excellent summary&lt;/a&gt; of its most essential features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typescript
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Types good, boilerplate code bad&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;JavaScript is a loosely typed language, also known as a dynamically typed language, which means that it is flexible and does type checking on run-time rather than compile time. This means you can create a variable that starts as string type, but then change it to a number type, etc.&lt;br&gt;
For many people that have started programming in C or Java, this is horrifying (ergo coercion memes on Reddit), as those languages are pretty strict with types and require a full definition of data type or interface for a constant. Anyway, there’s a lot to love about static types: static types can be beneficial to help document functions, clarify usage, and reduce cognitive overhead. But, IMO, there’s a lot to love about dynamic types as well.&lt;/p&gt;

&lt;p&gt;So, there comes Typescript. Typescript is Javascript, with an extra layer that adds static typing tools and capabilities to your Javascript code. As you’re developing an application, you will be writing Typescript code, which then gets compiled to plain JavaScript for the browser to understand. It &lt;strong&gt;can fix some of the issues&lt;/strong&gt; dynamically typed languages have, a big plus is if you use one of the TS supported editors (VS Code, Atom, Webstorm) which can provide the excellent dev experience and boost your productivity. That aside, I hate a boilerplate code that comes with TS. A few TS projects I've been working with have at least 30-40% more lines of code just because of TS types, interfaces and enums. Errors can be cryptic sometimes, and debugging type issues can get on a nerve real quick. Merged types and some advanced type definitions can be tiring to read and understand sometimes.&lt;/p&gt;

&lt;p&gt;There is a &lt;a href="https://medium.com/javascript-scene/the-typescript-tax-132ff4cb175b" rel="noopener noreferrer"&gt;excellent article&lt;/a&gt; by Eric Elliott about Typescript's bad and good sides if you want to read more. Still, my overall opinion of TS is positive, just because whenever I go back to read the code, I can (almost always!) understand immediately and thoroughly what each type of variable is, what this function returns, whether this array has been changed throughout the program, etc. That's a lot of saved time and minimized the number of redundant operations to check the type and structure of the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code testing, integration, and delivery
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Give a developer a tedious task, and they'll find a way to automate it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Probably most of us here are familiar with tools as Webpack, Gulp, Grunt, lint-staged. Even Prettier and Eslint are a kind of automation tool. The less time we spend on menial or repeating tasks, the more time we have to focus on the actual problems. &lt;/p&gt;

&lt;p&gt;Few developers get excited over the idea of writing tests for their code. Especially when there is a pressure to finish new features as fast as possible, writing test code that doesn’t directly contribute to the progress of the project can be annoying. When the project is small and you can test a few available features manually this might be fine, but once the project starts to grow manual testing is time-consuming and horribly inefficient.&lt;/p&gt;

&lt;p&gt;Investing in testing upfront is one of the best investments you can make on your project. It is what allows you to write a feature, not touch it for weeks, come back, see it is passing all its tests, and have a level of confidence that everything is good in the world. &lt;/p&gt;

&lt;p&gt;I've been using mostly &lt;a href="https://github.com/facebook/jest" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; for my tests, but I've heard good things about &lt;a href="https://github.com/ericelliott/riteway" rel="noopener noreferrer"&gt;Riteway&lt;/a&gt;. Testing React components have gotten more difficult since the introduction of the hooks, Enzyme is having a hard time so I'm not sure if I can recommend it anymore, &lt;a href="https://github.com/testing-library/react-testing-library" rel="noopener noreferrer"&gt;react-testing-library&lt;/a&gt; might be a better choice for now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous integration&lt;/strong&gt; is the software development practice of frequently integrating changes to a shared code repository. For every integration, automatic formatting and testing should be done. This gives the developers a quick feedback cycle for determining potential conflicts in commits while also allowing to frequently merge new updates to an application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous delivery&lt;/strong&gt; works in conjunction with CI to take the tested and built application that results from the CI process and deploy (or deliver) it to the intended infrastructure. With CD, teams can push new code to production every day or even hourly and get quick feedback on what users care about.&lt;/p&gt;

&lt;p&gt;A lot can be told about how to write tests and how to configure CI/CD pipeline but that would be a whole post on its own. There is no golden rule for how to write perfect tests but making sure that you at least write them and are trying to get ~80% coverage with a combination of unit, integration, and e2e tests should lead to clean and confident code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I always struggle with summaries (same thing with prefaces). For me, it's usually hard to start writing a post, after that, I can go on and on, same with deciding how to end it 😄 I still feel like I haven't wrote enough about all topics mentioned, so feel free to comment if you have any questions.&lt;/p&gt;

&lt;p&gt;Bear in mind that this is a half rant and half commentary to myself, after several years working with Js. There’s a whole class of internet comments that can be summarised as “I disagree, and that makes me ANGRY, here's a downvote”, which is a pity, because when two reasonable people disagree, there’s very often something interesting going on.&lt;/p&gt;

&lt;p&gt;Thanks for reading! &lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@adigold1?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Adi Goldstein&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/code-foo?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How are you writing a commit message?</title>
      <dc:creator>Darkø Tasevski</dc:creator>
      <pubDate>Tue, 08 Oct 2019 18:30:11 +0000</pubDate>
      <link>https://forem.com/puritanic/how-are-you-writing-a-commit-message-1ih7</link>
      <guid>https://forem.com/puritanic/how-are-you-writing-a-commit-message-1ih7</guid>
      <description>&lt;p&gt;I'll write a little bit about a topic that is not related to code, seemingly not that important, but it is quite practical in daily programming. &lt;strong&gt;How to write git commit message properly?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;No way of writing is right or wrong; however, if each person in a project has its own style of a commit message, then when we look at the commit history, does it look good? Not to mention when we need to search and review commits in the commit of the previous months through commit messages without any rules. &lt;/p&gt;

&lt;p&gt;Commits that are non-consistent in a message format or don't follow any rules can be a problem because of many reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading the commit message without knowing what the exact purpose of the commit was.&lt;/li&gt;
&lt;li&gt;When we need to summarize changes in source code after a period of development (e.g. production release)&lt;/li&gt;
&lt;li&gt;Choosing suitable new version,  v1.0.0, v1.0.1, v1.1.0 or v2.0.0 etc.&lt;/li&gt;
&lt;li&gt;Trying to search through commits via regex&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33lx2fc7hrsm1bh1kgnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33lx2fc7hrsm1bh1kgnp.png" alt="obligatory xkcd" width="439" height="250"&gt;&lt;/a&gt;&lt;/p&gt;
Obligatory xkcd



&lt;p&gt;Some teams decide to implement a kind of &lt;em&gt;closed&lt;/em&gt; rules to solve some of the issues caused by fragmented code commit message styles. Why closed you ask? That is because those are local to your project. For example, in my team, we had a convention to prefix commit messages and git branches with the ticket number.&lt;/p&gt;

&lt;p&gt;So are there any rules that are common to all of us and can be shared across many projects? &lt;strong&gt;Enter...&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conventional Commits
&lt;/h2&gt;

&lt;p&gt;Conventional Commits are a set of commit message writing rules that create rules that are easy to read for both machines and humans. The machines can make use of these rules, for example, in tools for automatic versioning.&lt;/p&gt;

&lt;p&gt;This set of rules corresponds to &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;SemVer&lt;/strong&gt;&lt;/a&gt; (Semantic Version) by the way it describes features, bug fixes, code refactors, or breaking changes made in commit messages. Currently, at the time of this writing, this set of rules Conventional Commits is in version &lt;a href="https://www.conventionalcommits.org/en/v1.0.0-beta.4/" rel="noopener noreferrer"&gt;1.0.0-beta.4&lt;/a&gt;, and there may be future additions. You can refer to the implementation of conventional commits in some open projects on Github, some of the bigger ones are &lt;a href="https://github.com/electron/electron" rel="noopener noreferrer"&gt;Electron&lt;/a&gt;, &lt;a href="https://github.com/istanbuljs/istanbuljs" rel="noopener noreferrer"&gt;IstanbulJs&lt;/a&gt;, &lt;a href="https://github.com/yargs/yargs" rel="noopener noreferrer"&gt;Yargs&lt;/a&gt; and &lt;a href="https://github.com/karma-runner/karma" rel="noopener noreferrer"&gt;Karma&lt;/a&gt; which, if I've understood correctly, actually laid &lt;a href="https://karma-runner.github.io/0.10/dev/git-commit-msg.html" rel="noopener noreferrer"&gt;foundations&lt;/a&gt; to semantic commits.&lt;/p&gt;

&lt;p&gt;The commit message should be structured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;type&amp;gt;[optional scope]: &amp;lt;description&amp;gt;

[optional body]

[optional footer]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are required by commit message, and all others are optional.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt;: keyword to classify if the commit was a feature, bugfix, refactor... Followed by the &lt;code&gt;:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scope&lt;/code&gt;: is used to categorize commits, but answers the question: what does this commit refactor | fix? It should be enclosed in parentheses immediately after &lt;code&gt;type&lt;/code&gt;, e.g. &lt;code&gt;feat(authentication):&lt;/code&gt;, &lt;code&gt;fix(parser):&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt;: a short description of what was modified in the commit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;body&lt;/code&gt;: is a longer and more detailed description, necessary when the description cannot fit one line:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: allow the provided config object to extend other configs

BREAKING CHANGE: &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;extends&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt; key in the config file is now used for extending other config files"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;footer&lt;/code&gt;: some extra information such as the ID of the pull request, contributors, issue number(s)...&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Some examples for a short commit message as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ex1: &lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: implement AVOD content reels"&lt;/span&gt;

&lt;span class="c"&gt;# ex2: &lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"fix: routing issue on the main page"&lt;/span&gt;

&lt;span class="c"&gt;# ex3 with scope: &lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix(player): fix player initialization"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Semantic Versioning
&lt;/h2&gt;

&lt;p&gt;Conventional Commit matches &lt;strong&gt;SemVer&lt;/strong&gt; through &lt;code&gt;type&lt;/code&gt; in the commit message.  Automated versioning tooling also relies on it to decide the new version for source code. With the following convention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fix&lt;/code&gt;: a commit of the (bug)fix type is equal to PATCH in the SemVer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;feat&lt;/code&gt;: a commit of type feature is equal to MINOR in the SemVer.&lt;/li&gt;
&lt;li&gt;Also, the keyword &lt;code&gt;BREAKING CHANGE&lt;/code&gt; in the &lt;code&gt;body&lt;/code&gt; section of the commit message will imply that this commit has a modification that makes the code no longer compatible with the previous version. Like changing the response structure of an API, the handle response part of the previous structure will of course no longer be accurate, and now we need to create an entirely new version by bumping MAJOR SemVer version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some common &lt;code&gt;type&lt;/code&gt; uses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;feat&lt;/code&gt;: a new feature for the user, not a new feature for a build script&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fix&lt;/code&gt;: bug fix for the user, not a fix to a build scripts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;refactor&lt;/code&gt;: refactoring production code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chore&lt;/code&gt;: updating gulp tasks etc.; no production code change&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs&lt;/code&gt;: changes to documentation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;style&lt;/code&gt;: formatting, missing semicolons, etc.; no code change&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;perf&lt;/code&gt;: code improved in terms of processing performance&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vendor&lt;/code&gt;: update version for dependencies, packages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test&lt;/code&gt;: adding missing tests, refactoring tests; no production code change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these are the most common types that you're going to see in the wild, nothing is stopping you from creating your own types of commits.&lt;/p&gt;

&lt;p&gt;Another bonus advantage from using semantic commits is that you can derive a sense of effort from git logs. For example, given below is a year worth of git commits in karma (master branch):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/karma
&lt;span class="nv"&gt;$ &lt;/span&gt;git log &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oneline &lt;span class="nt"&gt;--no-merges&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt; 2017/01/01 &lt;span class="nt"&gt;--until&lt;/span&gt; 2017/12/31 | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 2 |&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"("&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 1 | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 1 | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-nr&lt;/span&gt; &lt;span class="nt"&gt;-k1&lt;/span&gt;
     39 chore
     28 fix
     23 feat
     15 docs
      6 &lt;span class="nb"&gt;test
      &lt;/span&gt;2 Try
      1 refactor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the scope annotation we could further slice and dice this data into questions like which component has the most number of bug fixes?.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Commit messages must have a prefix of a type (noun form) such as feat, fix and so on, Immediately followed by scoped (if any), a colon and space.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"test: add missing tests for promo reels"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;feat&lt;/code&gt; This type is required to use when adding a feature&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fix&lt;/code&gt; This type is required to use when fixing a bug&lt;/li&gt;
&lt;li&gt;If there is scope, the scope must be a noun that describes the area of ​​the code change and must be placed immediately after type. Eg, feat(authentication). (Examples?)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"refactor(auth): improve refresh token logic"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The description must be a short description of the changes in the commit and must be after the type with or without a scope.&lt;/li&gt;
&lt;li&gt;A long commit can have the body right after the description, providing context for the changes. There must be a blank line between description and body.&lt;/li&gt;
&lt;li&gt;The footer can be placed immediately after the body, but there must be an empty line between the body and the footer. Footers should include extended information about commits such as related pull requests, reviewers, breaking changes. Each information on one line.&lt;/li&gt;
&lt;li&gt;Other types than &lt;code&gt;feat&lt;/code&gt; and &lt;code&gt;fix&lt;/code&gt; can be used in the commit messages.&lt;/li&gt;
&lt;li&gt;Committing breaking changes must be specified at the beginning of the body or footer with the capitalized &lt;code&gt;BREAKING CHANGE&lt;/code&gt;  keyword. Followed by a colon, space, and description. For example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat(OAuth): add scopes for OAuth apps  

BREAKING CHANGE: environment variables now take precedence over config files."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;An exclamation mark &lt;code&gt;!&lt;/code&gt; can be added before the &lt;code&gt;type/scope&lt;/code&gt;  to get attention and emphasize that the commit contains breaking change.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;So now, after the project is using Conventional Commits we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human readable project history&lt;/li&gt;
&lt;li&gt;measuring and classification of effort&lt;/li&gt;
&lt;li&gt;way to use Semantic Release to automate versioning and automatically generate changelogs for the project with semantic-release plugins.&lt;/li&gt;
&lt;li&gt;way to use Commit Lint to lint commit messages according to Conventional Commits.&lt;/li&gt;
&lt;li&gt;and finding, filtering and analyzing the commit history is also more straightforward when you can use the regex or git tools to filter commits by &lt;code&gt;type&lt;/code&gt; or &lt;code&gt;scope&lt;/code&gt; (or both).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Some useful links
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.conventionalcommits.org/en/v1.0.0-beta.4/#specification" rel="noopener noreferrer"&gt;https://www.conventionalcommits.org/en/v1.0.0-beta.4/#specification&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit" rel="noopener noreferrer"&gt;https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional" rel="noopener noreferrer"&gt;https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional&lt;/a&gt;&lt;br&gt;
&lt;a href="https://karma-runner.github.io/0.10/dev/git-commit-msg.html" rel="noopener noreferrer"&gt;https://karma-runner.github.io/0.10/dev/git-commit-msg.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://electronjs.org/docs/development/pull-requests#commit-message-guidelines" rel="noopener noreferrer"&gt;https://electronjs.org/docs/development/pull-requests#commit-message-guidelines&lt;/a&gt;&lt;br&gt;
&lt;a href="https://chris.beams.io/posts/git-commit/#seven-rules" rel="noopener noreferrer"&gt;https://chris.beams.io/posts/git-commit/#seven-rules&lt;/a&gt;&lt;br&gt;
&lt;a href="https://codito.in/semantic-commits-for-git" rel="noopener noreferrer"&gt;https://codito.in/semantic-commits-for-git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Really nice &lt;a href="https://unsplash.com/photos/842ofHC6MaI" rel="noopener noreferrer"&gt;Cover Photo by Yancy Min&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>git</category>
      <category>versioning</category>
    </item>
  </channel>
</rss>
