<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Miha Friskovec</title>
        <link>https://www.mihafriskovec.com</link>
        <description>Writing by Miha Friskovec</description>
        <lastBuildDate>Thu, 03 Sep 2026 18:25:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Miha Friskovec</title>
            <url>https://www.mihafriskovec.com/favicon.ico</url>
            <link>https://www.mihafriskovec.com</link>
        </image>
        <copyright>All rights reserved 2026</copyright>
        <item>
            <title><![CDATA[I Let AI Review My Pull Request — Here’s What Happened]]></title>
            <link>https://www.mihafriskovec.com/articles/ai-pull-request-review</link>
            <guid isPermaLink="false">https://www.mihafriskovec.com/articles/ai-pull-request-review</guid>
            <pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h3>PRs are slow and painful. Could AI make them faster, or just add&nbsp;noise?</h3>
<p>Code reviews are the blessing and curse of modern development. They keep bugs out, enforce standards, and make us all better developers.</p>
<p>They also block merges, drag on for days, and sometimes feel like nitpicking about whitespace.</p>
<p>So what if AI could take the first pass? Would that free up your teammates to focus on the hard stuff — or just add more&nbsp;noise?</p>
<h2>Why AI makes sense for code&nbsp;reviews</h2>
<p>Code reviews are often about repeatable patterns: obvious bugs, missed tests, insecure code, inconsistent style.</p>
<p>AI excels at spotting those quickly. Instead of spending 20 minutes pointing out missing error handling, you let a model flag it. Humans can then focus on design decisions, trade-offs, and architecture.</p>
<blockquote>
<p>“AI won’t replace your teammates, but it might stop them from pointing out missing semicolons.”</p>
</blockquote>
<p>The idea isn’t to remove humans from the loop — it’s to reduce review&nbsp;fatigue.</p>
<h2>Tools worth&nbsp;trying</h2>
<p>The AI code review space is still young, but a handful of tools are already useful in real-world workflows. Some focus on catching bugs, others on style or security, and a few try to cover everything.</p>
<p>Here’s a quick cheat sheet before we dive into&nbsp;details:</p>
<h2>Quick Comparison</h2>
<p>Think of this table as your “at a glance” guide. If you want quick inline comments, look at Copilot Reviewer. If your team wants structured PR feedback, CodeRabbit is built for that. And if security is your priority, CodeWhisperer or DeepCode might save you from nasty surprises.</p>
<p>Now, let’s break down what each of these tools actually does in practice.</p>
<h2>GitHub Copilot&nbsp;Reviewer</h2>
<p>Lives directly inside GitHub PRs, where Copilot can scan your changes and leave review-style comments. Great for pointing out inconsistent naming, unused variables, or small logic slips. Think of it as a linter with natural language. Don’t expect it to critique your architecture, but it can cut noise before human reviewers step&nbsp;in.</p>
<h2>CodeRabbit</h2>
<p>A dedicated AI reviewer built for PRs. It integrates with GitHub and GitLab, automatically reviews pull requests, and leaves structured comments. What makes it stand out is context awareness: it doesn’t just say “missing test,” it explains why and often suggests the test you should add. Teams report faster review cycles and less reviewer fatigue because AI handles the repetitive stuff.</p>
<h2>Amazon CodeWhisperer Security&nbsp;Scan</h2>
<p>Amazon baked security scanning into CodeWhisperer, targeting vulnerabilities that often slip past manual reviews. It looks for injection risks, hard-coded secrets, and unsafe API calls. In large teams working with sensitive data, it acts like a first defense line, surfacing potential red flags before code hits staging. It won’t fix your style, but it’s invaluable for keeping pipelines secure.</p>
<h2>Sourcery (for&nbsp;Python)</h2>
<p>Focused purely on Python refactoring. Instead of flagging bugs, it suggests ways to make code more efficient, readable, and idiomatic. For example, it might recommend replacing a loop with a list comprehension or pulling out repeated logic into a helper function. Many Python devs run it locally before pushing code so they don’t waste teammate time on style&nbsp;debates.</p>
<h2>DeepCode (by&nbsp;Snyk)</h2>
<p>DeepCode combines static analysis with AI, scanning both your code and dependencies. It looks for patterns of bad practice, outdated libraries, and potential vulnerabilities. For bigger projects, it acts like a safety net, catching issues that traditional linters or tests miss. The downside is noise: in smaller repos, you’ll spend more time filtering than&nbsp;fixing.</p>
<h2>How to choose the right&nbsp;tool</h2>
<p>Not every team needs the same kind of reviewer. Here’s a quick way to think about&nbsp;it:</p>
<ul>
<li><strong>Solo dev or indie hacker</strong> → Start with <strong>Sourcery</strong> (if you’re in Python) or <strong>Copilot Reviewer</strong>. They give you a “second pair of eyes” without slowing you&nbsp;down.</li>
<li><strong>Small teams</strong> → Try <strong>CodeRabbit</strong>. It’s structured enough to keep PRs consistent but light enough that it won’t add too much overhead.</li>
<li><strong>Enterprise or security-sensitive projects</strong> → Layer in <strong>CodeWhisperer Security Scan</strong> or <strong>DeepCode</strong>. They’re built to catch vulnerabilities before they become incidents.</li>
</ul>
<blockquote>
<p>“The best AI reviewer is the one that saves you time without killing team&nbsp;trust.”</p>
</blockquote>
<h2>The limitations</h2>
<p>AI reviews come with&nbsp;caveats.</p>
<p>They can be over-confident, suggesting changes that actually break working code. They lack system context, so they don’t see how modules fit together. And if you rely on them too heavily, team trust can suffer — people resist merging changes flagged <em>only</em> by a&nbsp;bot.</p>
<p>In short: AI can make your reviews faster and cleaner, but it can’t replace the human judgment that makes software&nbsp;robust.</p>
<h2>Wrapup</h2>
<p>AI won’t kill code reviews, and it shouldn’t. But it might finally make them less&nbsp;painful.</p>
<p>Start small: let a bot take the first pass, save your teammates from the obvious stuff, and focus human effort where it matters&nbsp;most.</p>
<p>Now I’m curious — <strong>have you let AI review your PRs yet? Which tool worked best for you?</strong> Drop your experience in the comments; I’d love to feature the most useful ones in a follow-up.</p>
<blockquote>
<p>“The best review culture is still human — but a little AI help can make it&nbsp;humane.”</p>
</blockquote>
<hr>
<p><em>Originally published on <a href="https://medium.com/swlh/ai-code-reviews-can-a-bot-really-replace-your-teammate-876c83a1f2a3">Medium</a>.</em></p>]]></content:encoded>
            <author>friskovec.miha@gmail.com (Miha Friskovec)</author>
        </item>
        <item>
            <title><![CDATA[I Tried 5 AI Testing Tools — Here’s What Actually Worked]]></title>
            <link>https://www.mihafriskovec.com/articles/ai-testing-tools</link>
            <guid isPermaLink="false">https://www.mihafriskovec.com/articles/ai-testing-tools</guid>
            <pubDate>Thu, 28 Aug 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h2>I Tried 5 AI Testing Tools — Here’s What Actually&nbsp;Worked</h2>
<h3>I ran real-world code through CodiumAI, Testim, QA Wolf, Diffblue, and GPT. Some impressed me, others wasted my&nbsp;time.</h3>
<p>Developers spend 30–50% of their time testing and debugging. It’s essential — but also slow, repetitive, and often the least glamorous part of the&nbsp;job.</p>
<p>So if AI can already write code, the obvious question is: <strong>can it test it&nbsp;too?</strong></p>
<p>I tried a handful of AI testing tools to see if they actually speed things up or just add noise. Here’s what I&nbsp;found.</p>
<h2>Why AI for testing makes&nbsp;sense</h2>
<p>Testing is repetitive by design. Unit tests check predictable logic, end-to-end (E2E) tests simulate user flows, regression tests re-run the same scripts over and&nbsp;over.</p>
<p>That’s exactly the kind of grunt work AI is well-suited for.</p>
<p>Instead of manually writing boilerplate tests, you can ask an AI&nbsp;to:</p>
<ul>
<li>Generate unit tests for existing functions</li>
<li>Suggest edge cases you didn’t think&nbsp;of</li>
<li>Automate user flows for E2E&nbsp;coverage</li>
</ul>
<blockquote>
<p>“AI testing doesn’t replace QA — it just gives you more coverage&nbsp;faster.</p>
</blockquote>
<p>But not all tools are created equal. Let’s break them&nbsp;down.</p>
<h2>Tools worth&nbsp;trying</h2>
<p>Not all AI testing tools solve the same problem. Some focus on unit test scaffolding, others on automating E2E flows, and a few are built for enterprise-level coverage. Here are the most notable&nbsp;ones.</p>
<h2>CodiumAI</h2>
<p>CodiumAI plugs into VS Code and JetBrains and generates unit tests based on your functions. It analyzes code paths, proposes edge cases, and builds the skeleton of your test suite for&nbsp;you.</p>
<p>Say you wrote this JavaScript function:</p>
<pre><code>function calculateDiscount(price, percentage) {
  if (percentage &lt; 0 || percentage &gt; 100) {
    throw new Error("Invalid discount percentage");
  }
  return price * (1 - percentage / 100);
}
</code></pre>
<p>CodiumAI might generate Jest tests like&nbsp;this:</p>
<pre><code>test('applies a 10% discount', () =&gt; {
  expect(calculateDiscount(100, 10)).toBe(90);
});
</code></pre>
<pre><code>test('no discount applied when percentage is 0', () =&gt; {
  expect(calculateDiscount(100, 0)).toBe(100);
});
</code></pre>
<pre><code>test('throws error for negative percentage', () =&gt; {
  expect(() =&gt; calculateDiscount(100, -5)).toThrow();
});
</code></pre>
<pre><code>test('throws error for percentage over 100', () =&gt; {
  expect(() =&gt; calculateDiscount(100, 120)).toThrow();
});
</code></pre>
<p>It’s the kind of tool you’d use when you want quick test coverage without hand-writing every case, but you still need to sanity-check its output — especially if your business logic has edge cases CodiumAI can’t understand.</p>
<h2>Testim (by Tricentis)</h2>
<p>Testim is an AI-powered platform for building <strong>end-to-end (E2E) tests</strong>. Instead of hand-coding Selenium, you can visually record user flows (like login, checkout, or search), and Testim stabilizes the selectors so tests are less brittle when the UI&nbsp;changes.</p>
<p>For example, you record a login flow: open /login, type in email and password, click “Login,” and check that the dashboard loads. Testim generates the test for you and re-runs it every time you&nbsp;deploy:</p>
<pre><code>it('should log in successfully', async () =&gt; {
  await page.goto('/login');
  await page.fill('#email', 'user@test.com');
  await page.fill('#password', 'password123');
  await page.click('#login-btn');
  await expect(page).toHaveURL('/dashboard');
}); 
</code></pre>
<p>It’s a good fit for teams who run a lot of regression tests on web apps and don’t want brittle scripts breaking every week. Just don’t expect it to be completely hands-off — dynamic apps still need human oversight.</p>
<h2>QA Wolf</h2>
<p>QA Wolf isn’t just a tool — it’s <strong>testing-as-a-service</strong>. You describe a scenario (“a user adds an item to the cart, checks out, and receives a confirmation email”), and QA Wolf creates and maintains the E2E tests for you using Playwright under the&nbsp;hood.</p>
<p>That means if your UI changes, their human-in-the-loop testers update the scripts. You get coverage without burning dev time on writing or fixing&nbsp;tests.</p>
<p>Here’s a generated snippet from a checkout&nbsp;flow:</p>
<pre><code>test('user can complete checkout flow', async ({ page }) =&gt; {
  await page.goto('/');
  await page.click('text=Add to Cart');
  await page.click('text=Checkout');
  await page.fill('#email', 'test@qa.com');
  await page.click('text=Place Order');
  await expect(page.locator('h1')).toHaveText('Order Confirmed');
});
</code></pre>
<p>It’s great for small teams or startups who don’t have QA engineers on staff. The tradeoff is that you’re outsourcing part of your testing, so you lose a bit of&nbsp;control.</p>
<h2>Diffblue (for&nbsp;Java)</h2>
<p>Diffblue is built specifically for <strong>Java</strong> and auto-generates JUnit tests at scale. Enterprises with massive Java codebases use it to bootstrap coverage, especially for legacy&nbsp;code.</p>
<p>Take this simple&nbsp;method:</p>
<pre><code>public double calculateInterest(double principal, double rate, int years) {
    return principal * Math.pow(1 + rate, years);
}
</code></pre>
<p>Diffblue might generate:</p>
<pre><code>@Test
public void testCalculateInterest_basic() {
    assertEquals(1102.5, calculateInterest(1000, 0.05, 2), 0.01);
}
</code></pre>
<pre><code>@Test
public void testCalculateInterest_zeroPrincipal() {
    assertEquals(0, calculateInterest(0, 0.05, 5), 0.01);
}
</code></pre>
<pre><code>@Test
public void testCalculateInterest_negativeRate() {
    assertEquals(902.5, calculateInterest(1000, -0.05, 2), 0.01);
}
</code></pre>
<p>If you’re working in a big Java shop, this saves months of tedious work. But if you’re not using Java, it won’t do anything for&nbsp;you.</p>
<h2>ChatGPT / Copilot Chat /&nbsp;Cursor</h2>
<p>These aren’t dedicated QA platforms, but they’re surprisingly handy as <strong>test assistants</strong>. Paste in a function and ask for tests, and they’ll give you a starting&nbsp;point.</p>
<p>Take this palindrome checker:</p>
<pre><code>function isPalindrome(str) {
  return str === str.split('').reverse().join('');
}
</code></pre>
<p>GPT might suggest tests&nbsp;like:</p>
<pre><code>test('detects racecar as palindrome', () =&gt; {
  expect(isPalindrome('racecar')).toBe(true);
});
</code></pre>
<pre><code>test('detects hello as not palindrome', () =&gt; {
  expect(isPalindrome('hello')).toBe(false);
});
</code></pre>
<pre><code>test('empty string is palindrome', () =&gt; {
  expect(isPalindrome('')).toBe(true);
});
</code></pre>
<p>It’s not perfect — sometimes it suggests redundant or irrelevant tests — but for quick ideas, especially in solo projects, it’s a time-saver. You just wouldn’t rely on it as your main QA&nbsp;process.</p>
<h2>Where AI testing helps&nbsp;most</h2>
<ul>
<li><strong>Unit test coverage:</strong> AI can quickly cover basic branches you might&nbsp;forget.</li>
<li><strong>Edge case discovery:</strong> Suggests inputs and scenarios you wouldn’t think&nbsp;of.</li>
<li><strong>QA co-pilot:</strong> For solo devs or small teams, it’s like having a second pair of eyes without the overhead.</li>
</ul>
<h2>Where it still&nbsp;fails</h2>
<p>AI still struggles with:</p>
<ul>
<li><strong>False positives:</strong> Suggesting meaningless or redundant tests.</li>
<li><strong>System context:</strong> It can’t fully understand how components interact across the&nbsp;system.</li>
<li><strong>Maintenance:</strong> Generated tests still need updating when the code&nbsp;changes.</li>
</ul>
<blockquote>
<p>“AI can write your first 80% of tests — but the last 20% still needs human judgment.”</p>
</blockquote>
<h2>How to choose the right&nbsp;tool</h2>
<p>Different workflows call for different tools. Here’s the quick breakdown:</p>
<ul>
<li>If you’re a <strong>solo dev or indie hacker</strong>, CodiumAI or ChatGPT will save you time on unit&nbsp;tests.</li>
<li>If you’re a <strong>small team</strong>, Testim or QA Wolf can handle regression and E2E coverage.</li>
<li>If you’re an <strong>enterprise stuck with a massive Java codebase</strong>, Diffblue is basically built for&nbsp;you.</li>
</ul>
<h2>Wrapup</h2>
<p>Testing won’t disappear, and AI won’t make QA obsolete. But it can make the process less&nbsp;painful.</p>
<p>Instead of writing boilerplate for hours, you let AI scaffold the basics. You still refine, validate, and handle the edge cases that only a human with context can understand.</p>
<p>Your teammates will still argue about architecture decisions — but at least you won’t be wasting time on repetitive test&nbsp;scripts.</p>
<p>Now I’m curious — <strong>have you tried letting AI generate tests for your code? Which tool actually helped, and which one wasted your time?</strong> Drop your experience in the comments — I’d love to feature real-world feedback in a follow-up.</p>
<blockquote>
<p>“AI testing can help you write tests, but the responsibility is still in&nbsp;you”</p>
</blockquote>
<hr>
<p><em>Originally published on <a href="https://medium.com/javarevisited/i-tried-5-ai-testing-tools-heres-what-actually-worked-55598cf96c20">Medium</a>.</em></p>]]></content:encoded>
            <author>friskovec.miha@gmail.com (Miha Friskovec)</author>
        </item>
        <item>
            <title><![CDATA[Stop Celebrating AI Productivity: You’re Just Generating Technical Debt Faster]]></title>
            <link>https://www.mihafriskovec.com/articles/stop-celebrating-ai-productivity</link>
            <guid isPermaLink="false">https://www.mihafriskovec.com/articles/stop-celebrating-ai-productivity</guid>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h3>AI didn’t supercharge your engineering team. It automated technical debt — and you’re celebrating it as productivity.</h3>
<p>Your velocity charts look incredible. Pull requests are flying. GitHub Copilot shows 88% code acceptance rates and developers report 55% faster task completion. The quarterly board meeting slides are ready to boast about “efficiency gains.”</p>
<p>Then reality hits at code&nbsp;review.</p>
<p>A single pull request that should have taken 20 minutes now takes an hour. The AI-generated code is syntactically correct but architecturally questionable. Security vulnerabilities slip through because reviewers are overwhelmed by&nbsp;volume.</p>
<p>You haven’t solved the engineering bottleneck; you’ve just moved it. You’re celebrating a productivity illusion while unknowingly weaponizing technical debt.</p>
<h2>The Productivity Paradox: Speed vs.&nbsp;Delivery</h2>
<p>The numbers tell a seductive story. Research confirms that developers complete individual tasks 55% faster with AI assistance, and users accept nearly 30% of suggested code.</p>
<p>But this creates a dangerous disconnect between <em>coding speed</em> and <em>shipping&nbsp;speed</em>.</p>
<p>While individual output skyrockets, the team’s actual delivery timeline often remains flat or even worsens. Studies tracking over 10,000 developers across 1,255 teams revealed a brutal truth: while developers merge 98% more pull requests, overall delivery timelines remain unchanged or increase.</p>
<p>Why? Because you’ve shifted the workload to the most expensive part of the&nbsp;process:</p>
<ul>
<li>The Review Bottleneck: PR review time has increased by 91% in some organizations. AI-generated pull requests are often larger and structurally unfamiliar. A European logistics company found that Copilot-heavy PRs took 26% longer to review than human-written code.</li>
<li>The Churn Trap: Developers are adding and copying more code while refactoring less. This creates bloated, fragile codebases. Some organizations have seen code churn nearly double after heavy AI adoption.</li>
</ul>
<p>The takeaway for leaders: If your velocity is up but your deployment frequency is flat, you aren’t moving faster. You’re just spinning your wheels with higher friction.</p>
<h2>The Security Blind Spot: 45% Failure&nbsp;Rate</h2>
<p>Let’s talk about the risk profile you are accepting into your codebase.</p>
<p>AI-generated code introduces security vulnerabilities in 45% of cases. When presented with a choice between secure and insecure coding methods, generative AI models opted for the insecure option 45% of the&nbsp;time.</p>
<p>This isn’t a marginal risk — it is baked into the models’ pattern-matching nature. The data breaks down into alarming specific categories:</p>
<ul>
<li>Language-Specific Risks: Java code generated by AI has a 72% security failure rate, while Python and C# range between&nbsp;38–45%.</li>
<li>Attack Vectors: AI fails to secure code against cross-site scripting (XSS) 86% of the time and allows log injection attacks 88% of the&nbsp;time.</li>
</ul>
<p>The problem is compounded by false confidence. ChatGPT 3.5 correctly identified coding errors only 46.2% of the time. More than half the time, it missed the error entirely.</p>
<p>For a CTO, this means your team is shipping functional-looking code that is actually a latent security liability. The cost to fix these vulnerabilities in production will be exponentially higher than preventing them&nbsp;now.</p>
<h2>The Budget Impact: Financing Your Own&nbsp;Debt</h2>
<p>Technical debt is not an abstract concept; it is a line item eating your&nbsp;budget.</p>
<p>Organizations already allocate up to 40% of their IT budget to technical debt. McKinsey data suggests 10–20% of new product innovation budget is routinely redirected to servicing this&nbsp;debt.</p>
<p>AI adoption at scale threatens to inflate these costs significantly.</p>
<ul>
<li>Maintenance Overhead: Engineers already spend 2–5 days per month debugging and refactoring debt. AI code — often verbose and lacking context — adds to this&nbsp;pile.</li>
<li>The Iteration Cost: One study found that iterative AI code refinement actually increases critical vulnerabilities by 37.6% after five iterations. You are paying developers to use AI to fix AI-generated problems, creating a cycle of&nbsp;waste.</li>
</ul>
<p>Every line of unoptimized, context-blind AI code you merge is a loan taken out against your future Q3&nbsp;roadmap.</p>
<h2>The Human Cost: The “Reviewer Burnout”</h2>
<p>Consider “Alex,” a senior engineer on your team. Before AI, Alex spent 30% of her time reviewing junior code that was usually small and&nbsp;focused.</p>
<p>Now, she faces massive, AI-generated PRs that <em>look</em> right but contain subtle logical hallucinations. She can’t trust any of it. Over 40% of developers report little to no trust in AI-generated code, meaning they are second-guessing every&nbsp;line.</p>
<p>This leads to specific organizational dysfunction:</p>
<ul>
<li>Skill Atrophy: Experienced developers perceive their own secure coding proficiency decreases when relying on AI&nbsp;tools.</li>
<li>Cognitive Overload: The constant vigilance required to catch subtle AI errors contributes significantly to developer fatigue.</li>
<li>The Mentorship Gap: Junior developers are learning from AI prompts, not from Alex. They optimize for “getting it done,” effectively becoming prompt engineers rather than software architects.</li>
</ul>
<h2>The Architecture Reality&nbsp;Check</h2>
<p>AI works brilliantly in rigid, well-architected systems where patterns are established and enforced. Unfortunately, that describes almost no real-world startup codebase.</p>
<p>In most environments, AI code generation tools create chaos because they lack architectural context. They generate plausible code, not optimal system design. This results&nbsp;in:</p>
<ul>
<li>Duplicate Logic: Code blocks scattered across services without awareness of shared libraries.</li>
<li>Loss of Cohesion: Context-blind solutions that work in isolation but degrade the overall system integrity.</li>
<li>Fragility: Modules that no senior developer wants to touch because they lack a coherent human design strategy.</li>
</ul>
<p>Security performance has remained unchanged over time despite model updates. The tools aren’t getting better at understanding <em>system intent</em> — they are just getting faster at&nbsp;syntax.</p>
<h2>The Path Forward: Manage the Tool, Don’t Worship&nbsp;It</h2>
<p>You cannot ignore AI, nor should you. The efficiency gains are real <em>if</em> managed correctly. But you must stop celebrating raw speed and start managing the&nbsp;output.</p>
<ul>
<li>Measure the Right Metrics: Stop looking at lines of code or PR volume. Track delivery time, review duration, and re-work rate. If these aren’t improving, you’re generating debt.</li>
<li>Enforce “AI Scrutiny” Protocols: Treat AI-generated code as a high-risk contribution. Mandate stricter review checklists and automated security scanning specifically for LLM-generated patterns.</li>
<li>Invest in Architecture First: AI cannot fix a bad system. Double down on modular design and clear patterns. If your architecture is solid, AI will follow it. If it’s messy, AI will multiply the&nbsp;mess.</li>
<li>Establish Model Governance: Don’t just let any model run wild. Standardize the tools and versions your team uses to ensure consistent behavior and security standards.</li>
<li>Explicitly Budget for Refactoring: Allocate specific sprint time to pay down the “AI tax.” The time saved in generation must be reinvested in hardening and clean-up.</li>
</ul>
<h2>Conclusion</h2>
<p>AI didn’t invent technical debt. It just accelerated it.</p>
<p>Technical debt has always accumulated when teams optimize for speed over sustainability. AI simply makes that trade-off invisible — until deployment day. Your velocity metrics look great, but your engineering reality is becoming&nbsp;brittle.</p>
<p>Real productivity isn’t about how fast you write code. It’s about how reliably you ship value. Until your metrics reflect that distinction, you aren’t building the future — you’re just borrowing against&nbsp;it.</p>
<hr>
<p><em>Originally published on <a href="https://medium.com/@miha2255/stop-celebrating-ai-productivity-youre-just-generating-technical-debt-faster-8db0e0e5c1dc">Medium</a>.</em></p>]]></content:encoded>
            <author>friskovec.miha@gmail.com (Miha Friskovec)</author>
        </item>
        <item>
            <title><![CDATA[Java is Finally Over: Why Kotlin Won the War in 2026]]></title>
            <link>https://www.mihafriskovec.com/articles/why-kotlin-won</link>
            <guid isPermaLink="false">https://www.mihafriskovec.com/articles/why-kotlin-won</guid>
            <pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>What Kotlin now offers beyond “better Java” — and why it’s becoming the default choice for serious JVM&nbsp;work.</p>
<p>The last time a NullPointerException ruined a weekend release, it became obvious the language wasn’t helping—it was getting in the way. Kotlin is one of the few JVM languages that doesn’t just promise to reduce this kind of pain; it quietly removes an entire category of mistakes from your day-to-day work.​</p>
<p>As 2026 approaches, Kotlin has moved from “nice alternative to Java” to “default choice” for many Android, backend, and multiplatform teams. The features below aren’t just shiny syntax — they’re the reasons teams report cleaner codebases, faster onboarding, and fewer late-night debugging sessions.​</p>
<h2>Features That Save You From&nbsp;Bugs</h2>
<h3>Null safety: Kotlin’s quiet superpower</h3>
<p>Kotlin’s type system makes nullability explicit: a variable is non-null by default, and you must opt into nullable types with&nbsp;?, which forces you to handle the null case at compile time. This design dramatically reduces accidental NullPointerExceptions and pushes you toward safer patterns like safe calls (?.) and the Elvis operator (?:) instead of hoping tests catch everything.​</p>
<pre><code>val name: String? = fetchName()
val length = name?.length ?: 0
</code></pre>
<p>In practice, this means fewer “it crashed in production but not on my machine” incidents and cleaner, more honest APIs — especially when collaborating across&nbsp;teams.</p>
<h3>Sealed hierarchies instead of fragile&nbsp;enums</h3>
<p>Sealed classes let you model a closed set of states — success, error, loading, unauthorized — and then force your when expressions to handle every case. If you add a new subtype and forget to update your logic, the compiler reminds you instead of leaving a hidden runtime&nbsp;bug.​</p>
<pre><code>sealed class LoginState {
    data class Success(val userId: String) : LoginState()
    data class Error(val message: String) : LoginState()
    object Loading : LoginState()
}
</code></pre>
<p>Used for UI states, API responses, and workflows, sealed classes give you the confidence that your state machine can’t silently drift out of sync as the app evolves.​</p>
<h3>Destructuring and guards for readable control&nbsp;flow</h3>
<p>Destructuring declarations let you unpack objects into local variables in a single line, while guard conditions in when expressions (available in modern Kotlin versions) let you attach conditions directly to branches rather than nesting if statements.​</p>
<pre><code>when (result) {
    is LoginState.Success -&gt; {
        val (userId) = result
        welcomeUser(userId)
    }
    is LoginState.Error -&gt; showError(result.message)
}
</code></pre>
<p>The end result is business logic that reads like a set of clear rules rather than a maze of nested conditions — especially powerful when combined with sealed&nbsp;classes.</p>
<h2>Features That Make Code Smaller (Without Being&nbsp;Clever)</h2>
<h3>Data classes: the right kind of&nbsp;“magic”</h3>
<p>With data classes, one line of code gives you equals, hashCode, toString, copy, and destructuring for free. This is ideal for DTOs, API models, and value objects where you care about the data, not the ceremony.​</p>
<pre><code>data class User(val id: Int, val name: String, val email: String)

val user = User(1, "Ana", "ana@example.com")
val updated = user.copy(email = "new@example.com")
</code></pre>
<p>Teams adopting Kotlin often notice that their “model” layer becomes dramatically smaller and easier to reason about, which pays off when refactoring or debugging data&nbsp;flows.</p>
<h3>Extension functions: smarter utilities without static helper&nbsp;clutter</h3>
<p>Extension functions let you “add” methods to existing types — like String or List—without inheritance or wrappers.</p>
<pre><code>fun String.isValidEmail(): Boolean =
    contains("@") &amp;&amp; contains(".")

fun &lt;T&gt; List&lt;T&gt;.middleOrNull(): T? =
    if (size &gt; 1) this[size / 2] else null
</code></pre>
<p>Instead of scattering utility logic across static helper classes, you keep behavior close to where it’s used. Over time, your codebase grows a small, focused vocabulary tailored to your domain, which is easier for new teammates to pick&nbsp;up.</p>
<h3>Scope functions: configuration that actually reads&nbsp;well</h3>
<p>Functions like apply, also, run, and let help you configure and transform objects without repeating variable&nbsp;names.​</p>
<pre><code>val user = User(id = 1, name = "Ana").apply {
    email = "ana@example.com"
}
</code></pre>
<p>When used thoughtfully (rather than everywhere), scope functions turn common setup and transformation code into compact, readable snippets that reduce noise without sacrificing clarity.​</p>
<h3>DSL building blocks: code that replaces config&nbsp;files</h3>
<p>Kotlin’s combination of extension functions, lambdas with receivers, and @DslMarker annotations makes it a natural fit for type-safe DSLs. Many libraries now expose Kotlin-first configuration APIs that feel more like describing what you want than wiring objects&nbsp;together</p>
<pre><code>routing {
    get("/health") {
        call.respondText("OK")
    }
}
</code></pre>
<p>Compared to long YAML or JSON configs, these DSLs give you auto-completion, refactoring support, and compile-time checks — while staying readable for non-experts on the&nbsp;team.</p>
<h2>Features That Unlock Modern Architectures</h2>
<h3>Coroutines and Flow: async without callback&nbsp;hell</h3>
<p>Coroutines provide a structured way to write asynchronous code that looks sequential, using suspend functions instead of deeply nested callbacks. Paired with Flow for reactive streams, they power everything from pagination and live updates in Android apps to high-throughput backend services.​</p>
<pre><code>suspend fun fetchUserAndPosts() = coroutineScope {
    val user = async { api.fetchUser() }
    val posts = async { api.fetchPosts() }
    user.await() to posts.await()
}
</code></pre>
<p>Because coroutines integrate cleanly with popular frameworks (especially on Android), you get modern concurrency patterns without rewriting your stack around a specific reactive library.​</p>
<h3>Compose Multiplatform: one mental model for many platforms</h3>
<p>Compose Multiplatform lets you build UIs for Android, desktop, and the web (with iOS support evolving) using the same declarative API. Recent releases have pushed web support into a more practical state and improved performance and component quality across platforms.​</p>
<pre><code>@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}
</code></pre>
<p>For teams maintaining multiple frontends, this means fewer duplicated concepts and more shared UI logic, even if you still keep platform-specific polish where it&nbsp;matters.</p>
<h3>Context parameters: previewing cleaner dependency handling</h3>
<p>Kotlin 2.2 introduced context parameters (in preview), which let you declare certain dependencies — like a logger, locale, or configuration — as part of an implicit context rather than explicit parameters everywhere. Conceptually, it feels like moving cross-cutting concerns from “plumbing” in every function signature to a clearly defined ambient&nbsp;scope.​</p>
<pre><code>context(Logger)
fun processOrder(id: String) {
    log("Processing order $id")
}
</code></pre>
<p>As this feature matures in 2026, it’s likely to influence how teams structure dependency injection, logging, and transaction boundaries, especially in larger systems.​</p>
<h2>Looking Ahead to Kotlin in&nbsp;2026</h2>
<p>Kotlin’s evolution over the last few years has been less about flashy syntax and more about removing friction: fewer null-related crashes, leaner models, safer state handling, and a more coherent story for concurrency and cross-platform UI. The ongoing work on the K2 compiler, multiplatform tooling, and experimental features like context parameters suggests the language is being shaped with long-lived, complex codebases in&nbsp;mind.​</p>
<p>If you’re already on the JVM, learning Kotlin in 2026 isn’t a leap into the unknown — it’s a way to write the code you’re already writing with fewer sharp edges and better long-term maintainability. And if you do make the jump, you’ll likely find that one of these ten features quietly becomes the reason you never want to go&nbsp;back.</p>
<hr>
<p><em>Originally published on <a href="https://medium.com/javarevisited/java-is-finally-over-why-kotlin-won-the-war-in-2026-a804676acf9e">Medium</a>.</em></p>]]></content:encoded>
            <author>friskovec.miha@gmail.com (Miha Friskovec)</author>
        </item>
    </channel>
</rss>