<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by fResult on Medium]]></title>
        <description><![CDATA[Stories by fResult on Medium]]></description>
        <link>https://medium.com/@fresult?source=rss-495fbfb852f0------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Lz9qdLWxSCAaxNy-OyXTaw.jpeg</url>
            <title>Stories by fResult on Medium</title>
            <link>https://medium.com/@fresult?source=rss-495fbfb852f0------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 03:19:43 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@fresult/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[The Quick-Fix Trap in Security Scans: Why Pinning Dependency Versions Is Not a Good Remediation…]]></title>
            <link>https://fresult.medium.com/the-quick-fix-trap-in-security-scans-why-pinning-dependency-versions-is-not-a-good-remediation-a8bb58f90186?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/a8bb58f90186</guid>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[dependency-management]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Fri, 25 Sep 2026 15:31:50 GMT</pubDate>
            <atom:updated>2026-10-28T19:59:49.627Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>The Quick-Fix Trap in Security Scans:</strong> <em>Why Pinning Dependency Versions Is Not a Good Remediation Strategy</em></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sn63tfswL4SUwhJN" /><figcaption>Photo by <a href="https://unsplash.com/@truckrun_ebike_systems?utm_source=medium&amp;utm_medium=referral">TruckRun</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Your application may work for years without a framework upgrade.<br>But the libraries it uses can still have known vulnerabilities, even when nothing appears broken.</p><p><strong>I have seen the same response to security findings in several projects.</strong><br>A scanner reports a vulnerable library and a fixed version.<br>Someone adds that version directly to the build before checking where the library came from or who manages its version.</p><p>This is part of software supply chain security: we need to know which libraries we use, how we choose their versions, and what we actually ship.</p><p>I understand the urgency of fixing the finding.<br>It needs attention, and pinning the reported version looks like a quick fix.<br>But an override left in the build can make later upgrades harder to manage.</p><p>In this article, I use Spring Boot as an example.<br>After building an executable JAR, I run <a href="https://anchore.com/grype">Grype</a> against the project directory, which includes the built JAR.<br>This gives me quick feedback after a change, without building or deploying a container image.</p><pre># My `catalog-service` directory contains `build/libs/catalog-service*.jar` executable file<br>→ grype dir:./catalog-service<br> ✔ Scanned for vulnerabilities     [3 vulnerability matches]  <br>   ├── by severity: 3 critical, 0 high, 0 medium, 0 low, 0 negligible<br>   └── by status:   3 fixed, 0 not-fixed, 0 ignored <br>SEVERITY  NAME               INSTALLED  FIXED IN  TYPE  VULNERABILITY   EPSS         RISK   <br>Critical  tomcat-embed-core  11.0.24    11.0.25   JAR   CVE-2026-65905  0.8% (53rd)  0.7    <br>Critical  tomcat-embed-core  11.0.24    11.0.25   JAR   CVE-2026-68525  0.6% (48th)  0.6    <br>Critical  tomcat-embed-core  11.0.24    11.0.25   JAR   CVE-2026-65182  0.6% (44th)  0.5</pre><p>The report shows tomcat-embed-core at 11.0.24 and lists 11.0.25 as a fixed version.<br>Here, “3 fixed” means fixes are available.<br>It doesn’t mean the three vulnerabilities have already been fixed in the files I scanned.</p><p>The tempting next change is to add tomcat-embed-core:11.0.25 directly to the build.<br>But the report doesn’t tell me whether the framework or another part of the build manages that version.</p><blockquote><em>Many frameworks manage a set of library versions for us.</em></blockquote><p><strong>The same is true for Spring Boot.</strong><br>It manages Tomcat as part of its dependency set.<br>If I change only tomcat-embed-core, other Tomcat modules may remain on older versions.<br>The override may also keep taking priority after a later Spring Boot release updates Tomcat.<br>I would then need to find and remove it myself.</p><p>So before changing the build, I check who manages the library, which supported release includes the fix, and which versions the build actually selects.</p><p>Scanning the built files helps me quickly check changes.<br>I still scan the final container image because it can contain other packages from its base image.</p><blockquote><strong><em>💡 TIP:<br></em></strong>grype is the vulnerability scanner tool I use here.<br>The same questions (in the next section) apply when a finding comes from another scanner.</blockquote><h3>How I Would Handle the Finding</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tEXWmsQl5_66znC72ghghg.png" /><figcaption><em>Decision process for evaluating and remediating dependency vulnerabilities.</em></figcaption></figure><p><strong>1. Find the affected component.</strong><br>Read the CVE and the fixed version.<br>Check whether the scanner found an application library or a package in the base image.<br>Check the libraries in the final image.<br>A change to the build will not fix an OS package in the base image.</p><p><strong>2. Find who manages its version.</strong><br>Look at the resolved runtime dependencies in the build tool.<br>Is the library managed by the framework?<br>Does another BOM manage it?<br>Did the project set an explicit version already?</p><p><strong>3. If the framework manages it, check a newer supported minor release first.</strong><br>Look at its support date and release notes.<br>Check the library version it manages.<br>If that version fixes the CVE, move to the new minor release and use a suitable patch from that line.<br>Test the change.<br>A patch in the old minor line doesn’t extend that line’s support date.</p><p><strong>4. If the new minor is unavailable or doesn’t contain the fix, check the current minor line.</strong><br>Has the framework released a patch that includes a fixed library version?<br>If so, upgrade to that patch.<br>If a new minor exists but we cannot finish that move before the security deadline, a fixed patch in the current line can also serve as the immediate fix.<br>Keep the minor upgrade on the work plan.</p><p><strong>5. If no framework release has the fix, consider a temporary override.</strong><br>Choose a fixed version that works with the application.<br>Check whether the library has related modules that should move together.<br>Write down the CVE, why the override exists, and when to review it.</p><p><strong>6. Verify the result.</strong><br>Check the resolved library versions and run the tests.<br>Build the application and scan its built files for quick feedback.<br>Then build the final container image and scan that image too.</p><p>Keep checking new framework minor and patch releases.<br>Remove temporary overrides when a supported framework release manages the fix.</p><p>For a Spring Boot project using Gradle Kotlin DSL and io.spring.dependency-management, a temporary Tomcat override can look like this:</p><pre>/*<br> * Temporary for CVE-2026-65905, CVE-2026-68525, and CVE-2026-65182.<br> * Remove when the chosen Spring Boot release manages the fix.<br> */<br>extra[&quot;tomcat.version&quot;] = &quot;11.0.25&quot;</pre><p>For a Maven project that inherits from spring-boot-starter-parent:</p><pre>&lt;properties&gt;<br>  &lt;tomcat.version&gt;11.0.25&lt;/tomcat.version&gt;<br>&lt;/properties&gt;</pre><p>These examples depend on the build setup.<br>The Gradle property above doesn’t work the same way with Gradle’s native platform(...).<br>Check the versions that the build <em>actually selects</em> before you build the image.</p><pre># Gradle:<br>./gradlew dependencyInsight \<br>   --dependency tomcat-embed-core \<br>   --configuration runtimeClasspath<br><br>./gradlew dependencies --configuration runtimeClasspath<br><br># Maven:<br>./mvnw dependency:tree -Dincludes=org.apache.tomcat.embed</pre><p>After fixing and building the executable file (JAR in this context)</p><pre>→ grype dir:./catalog-service<br> ✔ Scanned for vulnerabilities     [0 vulnerability matches]  <br>   ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible<br>   └── by status:   0 fixed, 0 not-fixed, 0 ignored </pre><h3>Why Regular Upgrades Matter</h3><p>Small, regular upgrades keep our application healthy.</p><p>When we upgrade often, each change stays small.<br>If something breaks, we have fewer changes to check.</p><p>If we wait too long, many releases pile up.<br>API changes, configuration updates, and library updates all arrive at once in one large migration.<br>The end-of-support date can make that migration into an urgent task under high pressure.</p><p>Keeping dependencies current also requires a way to spot updates.<br><a href="https://careersatdoordash.com/blog/reducing-the-migrations-pain-points/#:~:text=added%20linting%20to%20all%20our%20repositories%20to%20catch%20dependencies%20that%20need%20to%20be%20upgraded"><strong>DoorDash</strong></a> added linting to its repositories to catch dependencies that need upgrades.</p><p>For my Spring Boot projects, I check framework releases regularly.<br>I look for a release that manages a fixed version, so I can remove a temporary override.</p><p><strong>Example build:</strong> <a href="https://github.com/fResult/cloud-native-spring-in-action/blob/784b17d29038d97b0a796ebcace94b543aa040fa/Chapter08/catalog-service/build.gradle.kts#L32-L39">Spring Boot version and temporary Tomcat override</a>.</p><p><a href="https://github.com/fResult/cloud-native-spring-in-action/blob/784b17d29038d97b0a796ebcace94b543aa040fa/Chapter08/catalog-service/build.gradle.kts#L32-L39">cloud-native-spring-in-action/Chapter08/catalog-service/build.gradle.kts at 784b17d29038d97b0a796ebcace94b543aa040fa · fResult/cloud-native-spring-in-action</a></p><p>A scan finding tells us where to start.<br>Before changing a library version, we need to trace who manages it, choose a fix we can maintain, and check what we actually build and ship.</p><p>I shared a short visual summary on <a href="https://www.linkedin.com/feed/update/urn:li:activity:7509871601828515840">LinkedIn</a>.<br>If you have handled a similar finding in another framework, I would like to hear how you approached it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a8bb58f90186" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Contract of Failure (Part 1): Rethinking Exceptions with Either in Java]]></title>
            <link>https://fresult.medium.com/the-contract-of-failure-part-1-rethinking-exceptions-with-either-in-java-75f8a4e89486?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/75f8a4e89486</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[domain-driven-design]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Sun, 22 Mar 2026 09:52:35 GMT</pubDate>
            <atom:updated>2026-10-26T06:42:58.735Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>If you’re not a member,</strong><em><br></em><a href="https://fresult.medium.com/75f8a4e89486?source=friends_link&amp;sk=f14a30499991ed29794af3aeeaf1222c"><em>&gt;&gt;&gt;&gt;&gt; you can read for free here &lt;&lt;&lt;&lt;&lt;</em></a></p><h3>The Contract of Failure in Java Series</h3><ul><li><strong><em>Part 1: Rethinking Exceptions with </em></strong><strong><em>Either in Java (You are here)</em></strong></li><li><em>Part 2: Pattern Matching in Java, Trade-offs, and Team Culture (Coming soon)</em></li></ul><blockquote><strong><em>Before We Begin: A Note on “Functional Programming”<br></em></strong>Whenever I mention <em>Functional Programming (FP)</em> throughout this series, I need to clear the air: <strong>I am talking about the FP <em>discipline</em>, not hardcore FP syntax.</strong></blockquote><blockquote>I recently shared some thoughts on Kyle Simpson’s excellent post regarding this topic.<br>If you want a quick primer on why FP is about <strong>discipline rather than just syntax</strong>, you can check out my brief thoughts here:<br><a href="https://www.linkedin.com/feed/update/urn:li:activity:7440410591934148608/">https://www.linkedin.com/feed/update/urn:li:activity:7440410591934148608</a></blockquote><p>.</p><blockquote><strong><em>⚠️️ Disclaimer ⚠️:</em></strong><em> <br></em>The insights in this article come from my experimental sandbox, the “<a href="https://github.com/fResult/resilience-wallet-ledger">Resilience Wallet Ledger</a>”.<br>To be clear, I have not yet had the opportunity to implement this exact architecture in a real enterprise codebase.<br>Therefore, the anticipated benefits regarding team culture and incident communication are technically my hypotheses.</blockquote><blockquote><strong><em>However, these hypotheses are built on real battle scars.<br></em></strong>Recently, my team ported a decade-old VB.NET legacy system into a Java 17 Spring Boot 4.0 REST API.<br>We initially preserved its imperative style, deeply nested logic, and shared mutable states.<br>The real nightmare happened during a high-stress war room.<br>Manually tracing bugs through that messy imperative flow <strong>took us more than hour</strong>.</blockquote><blockquote><strong>This experience proved a harsh reality to us.</strong><br>Even when we upgrade to modern tools like Java 17, but sticking to the old imperative paradigm keeps our codebase just as unmaintainable as before.</blockquote><blockquote>Then, we decided to fight back.</blockquote><blockquote><strong>Armed with characterization tests as our safety net, we confidently refactored the system to decouple the legacy orchestration from the business rules.<br></strong>During this refactor, we discovered a severe architectural flaw: <strong>Polymorphic Data Design</strong>.<br>The legacy database used a single column to represent <em>both</em> a decimal monetary amount and an integer magic status code.<br>The absolute “WTF per minute” peak occurred when we found an SQL query executing SUM(transaction_<em>amount_code</em>) on this column to group duplicate records.</blockquote><blockquote><strong>We immediately flagged a critical logical flaw as a known issue to our stakeholders:</strong> If the system grouped 2 or more normal records with values of 7.00 and 3.00, the database would return 10.00.<br>Tragically, 10 was a magic number representing a CRITICAL business status.<br>The system was blindly misinterpreting normal financial sums as severe system flags!</blockquote><blockquote>We resolved this by adopting <strong>Domain-Driven Design (DDD)</strong> and <strong>Functional Programming</strong> principles.<br>We eliminated shared mutable states, isolated the database side-effects, and extracted 19 unreadable magic numbers into a functional <strong>Rule Engine</strong>.</blockquote><blockquote><strong>The value of this declarative approach was proven shortly after.</strong><br>When external partners asked us to find a portion-specific IDs to test complex status scenarios, we simply traced the new isolated rule engine and found the exact test data <strong>in a few minutes per ID</strong>.<br>We successfully brought dead legacy knowledge back to life.</blockquote><blockquote><strong>That significant reduction in cognitive load proved to me the value of explicit and declarative workflows.</strong><br>It inspired the vision for this article series: if clean code helps this much at the service level, enforcing strict architectural contracts will make a profound difference at the system level.<br><em>I believe these lessons are highly worth sharing with our engineering community.</em></blockquote><p><a href="https://github.com/fResult/resilience-wallet-ledger">GitHub - fResult/resilience-wallet-ledger: Practice Real-World Project for Distributed Systems using Kotlin, Vavr, and Spring Webflux</a></p><h3>Table of Content</h3><ul><li><a href="#56e6">Introduction: Reality Check &amp; The Goal</a></li><li><a href="#0827">The “WTF Per Minute” Metric &amp; The Invisible Trapdoor</a></li><li><a href="#7f70">The Railway Analogy</a></li><li><a href="#e09a">‘Either’ as a Legally Binding API Contract</a></li><li><a href="#cef8">Functional Core, Imperative Shell: Where Does the Contract Live?</a></li><li><a href="#504a">Wrap-Up: The Foundation is Set</a></li></ul><h3>The Reality Check</h3><p><strong>Let’s face it:<br>Java 21</strong> is out, and <strong>JEP 441 (Pattern Matching for </strong><strong>switch)</strong> is one of the best features in years.<br>It helps us handle complex state evaluations beautifully.</p><p>But I know the reality of the enterprise world.<br>Many of you are stuck maintaining systems on <strong>Java 17</strong>, <strong>Java 11</strong>, or even the forbidden <strong>Java 8</strong>.</p><p>You see cool functional programming paradigms, but you cannot use them in your legacy codebase.</p><p>This article is for you.</p><p>We can’t use the latest switch expressions in older Java versions.<br>But <strong>Java 17 (LTS)</strong> already gave us <strong>Pattern Matching for </strong><strong>instanceof (JEP 394)</strong>.<br>This is more than enough to completely change how we handle errors. And if you are still stuck with Java 8, don’t close this tab yet.<br>I will show you how to use the <strong>Vavr library</strong> to get the exact same safety.</p><p><a href="https://vavr.io">Vavr - Object-functional library for Java</a></p><h3>The Goal: Flattening the Control Flow</h3><p>I’m not here to sell you a magic trick that fixes all bugs.<br>The goal of this architectural shift is much more grounded: <strong>writing better</strong> <strong>and</strong> <strong>more predictable code</strong>.</p><p>How do we measure “better” code?<br>We measure it by the <strong>“WTFs per minute” </strong>metric during code reviews.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/0*WB6Rpau1zqXKmhuS" /><figcaption>The Only Valid Measurement of Code Quality: WTFs Per Minute</figcaption></figure><p>Our objective is to eliminate the hidden try-catch blocks scattered across our domain logic.<br>Right now, the execution flow probably jumps across dozens of lines due to unpredictable Exceptions.<br>We want to flatten that flow into a straight, boring, and highly predictable line.<br>We will transform errors from invisible trapdoors into explicit, manageable data.</p><h3>The “WTF Per Minute” Metric &amp; The Invisible Trapdoor</h3><p>When we read code and say “WTF”, it usually comes from two different places.</p><p><strong>First, it comes from real complexity.<br></strong>This is truly bad code.<br>Imagine an if-else block nested three (or more) levels deep, filled with mutating shared states.<br>You are forced to mentally compute and track how variables change at every step.<br>Or imagine a try-catch block wrapping your core business logic so tightly that you cannot find the actual logic.<br>Or a callback and lambda hell, where you cannot track the execution flow at all.<br>This is garbage complexity, and it deserves the &quot;WTF&quot;.</p><p><strong>Second, it comes from a skill gap.<br></strong>When a team first adopts new programming paradigms/concepts (in this case, Functional Programming (FP)), the code might look very alien.<br>Imperative developers will say “WTF” because they don’t know what the FP code actually does.<br><strong>This is completely normal.<br>It is a learning phase.</strong><br>But if we give up here and go back to writing messy imperative code just to deliver features quickly, we are making a huge mistake.<br>We are creating <a href="https://en.wikipedia.org/wiki/Heisenbug#:~:text=In%20computer%20programming,alters%20its%20state.">“Heisenbugs”</a> that may randomly explode in production six months later.<br>Closing this skill gap today is how we buy peace of mind for the future.</p><p>.</p><p>And the biggest source of bad complexity in Java is the traditional Exception.</p><p><strong>Let’s look at a standard method signature:</strong></p><pre>@NullMarked<br>public SuspendedWallet freezeForInvestigation(  <br>    String investigatorId, String walletId, String amlAlertId) { ... }</pre><p>It looks simple and honest, but it is lying to you.<br>Deep inside that function, someone might write throw new WalletAlreadySuspendedException().<br>Or they might throw throw new AmlAlertAlreadyResolvedException().</p><p>This is an <strong>invisible trapdoor</strong>.<br>When this line executes, the normal flow is interrupted.<br>It skips the next lines of code and jumps to a catch block somewhere else.<br>Maybe that block is dozens of lines later.<br>It might be three layers up in the same service.<br>Or even, it is five layers up in a GlobalExceptionHandler (@RestControllerAdvice).<br>You can’t determine this from the method signature alone.<br>You may have to spend hours reading the internal implementation to find out.</p><p><strong>Let’s look inside the actual implementation.</strong></p><pre>@NullMarked<br>public SuspendedWallet freezeForInvestigation(<br>    String investigatorId, String walletId, String amlAlertId) {<br><br>  // 1. Throw something if not found, for e.g.,<br>  //    - InvestigatorNotFoundException<br>  //    - WalletNotFoundException<br>  //    - AmlAlertNotFoundException<br>  final var investigator = investigatorRepository.findById(investigatorId).orElseThrow();<br>  final var wallet = walletRepository.findById(walletId).orElseThrow();<br>  final var alert = amlAlertRepository.findById(amlAlertId).orElseThrow();<br><br>  // 2. Guard Clauses (The immediate trapdoors)<br>  if (!investigator.hasComplianceRole()) {<br>    throw new UnauthorizedComplianceRoleException();<br>  }<br>  if (wallet.isSuspended()) {<br>    throw new WalletAlreadySuspendedException();<br>  }<br>  if (alert.isResolved()) {<br>    throw new AmlAlertAlreadyResolvedException();<br>  }<br><br>  // 3. The Illusion of Control (The massive try-catch)<br>  try {<br>    // ... [Imagine 30-40 lines of complex imperative logic here] ...<br>    // - Mutate local states first (e.g., alert.markAsProcessing)<br>    // - Call External Compliance API (Blocking, might timeout)<br>    // - Calculate freeze duration and update it to the `wallets` table<br>    // - Log success and send a notification<br>    return new SuspendedWallet(wallet.getId(), ...);<br><br>  } catch (HttpServerErrorException | ResourceAccessException ex) {<br>    // 4. The Teleportation Destination<br>    // The code jumped from the HTTP call down to here!<br>    // Local mutated states are now inconsistent. We log and re-throw a domain error<br>    throw new ExternalComplianceSystemException(&quot;Compliance system is down&quot;, ex);<br><br>  } catch (Exception ex) {<br>    // The ultimate sin: Catching generic Exception<br>    // We log the unknown failure and wrap it in a generic RuntimeException<br>    throw new RuntimeException(&quot;Unexpected error occurred while freezing wallet&quot;, ex);<br>  }<br>}</pre><blockquote><strong>Want to see the practical spaghetti code?<br>Check this out:<br></strong><a href="https://gist.github.com/fResult/3262e8a5809e1ad029802c698d7d543e#file-imperative1_walletapplicationservice-java">https://gist.github.com/fResult/3262e8a5809e1ad029802c698d7d543e#file-imperative1_walletapplicationservice-java</a></blockquote><p>Look at the method signature again.<br>It says it will return a SuspendedWallet.<br>But it actually does not.<br>It returns a SuspendedWallet, OR it crashes our execution flow with a runtime exception.<br>If the external API fails, the code teleports to the catch block, leaving our local variables in an inconsistent state.</p><p>This is what we call <strong>“The Lie”</strong>.<br>The method signature hides the true behavior of our domain logic.<br>These are the <strong>invisible trapdoors</strong>.<br>They wait quietly in the dark for someone to step on them.</p><p><strong>Let’s look at the actual caller.</strong></p><pre>@NullMarked<br>@RestController<br>@RequestMapping(&quot;/wallets&quot;)<br>public class WalletController {<br><br>  @PostMapping(&quot;/{id}/suspensions&quot;)<br>  public ResponseEntity&lt;SuspendedWallet&gt; freezeWallet(<br>      @PathVariable(&quot;id&quot;) String walletId,<br>      @RequestBody FreezeWalletRequest request,<br>      @AuthenticationPrincipal Jwt jwt) {<br><br>    // The Happy Path: It looks incredibly clean and linear.<br>    final var suspendedWallet = walletApplicationService.freezeForInvestigation(<br>        jwt.getSubject(), walletId, request.amlAlertId());<br><br>    return ResponseEntity.ok(suspendedWallet);<br>  }<br>}</pre><p><strong>Then, look at the exception handlers:</strong></p><pre>@RestControllerAdvice<br>public class GlobalExceptionHandler {<br>  // The Teleportation Destination: In a completely different file!<br><br>  // ... leave dozens of exception handlers for brevity ...<br><br>  @ExceptionHandler(UnauthorizedComplianceRoleException.class)<br>  public ProblemDetail handleUnauthorized(...) { ... }<br><br>  @ExceptionHandler({WalletAlreadySuspendedException.class, AmlAlertAlreadyResolvedException.class})<br>  public ProblemDetail handleDomainConflict(...) { ... }<br><br>  @ExceptionHandler(ExternalComplianceSystemException.class)<br>  public ProblemDetail handleExternalSystemFailure(...) { ... }<br><br>  @ExceptionHandler(Exception.class)<br>  public ProblemDetail handleGenericException(...) { ... }<br><br>  // ... leave another dozens of exception handlers for brevity ...<br>}</pre><blockquote><strong>If you are curious to see the full, messy reality of the exception handler,<br>Check this out:</strong><br>- <a href="https://gist.github.com/fResult/3262e8a5809e1ad029802c698d7d543e#file-imperative2_walletcontroller-java">https://gist.github.com/fResult/3262e8a5809e1ad029802c698d7d543e#file-imperative2_walletcontroller-java</a><br>- <a href="https://gist.github.com/fResult/3262e8a5809e1ad029802c698d7d543e#file-imperative3_globalexceptionhandler-java">https://gist.github.com/fResult/3262e8a5809e1ad029802c698d7d543e#file-imperative3_globalexceptionhandler-java</a></blockquote><p>Notice how clean the WalletController looks.<br>It seems like nothing can go wrong.<br>But when an exception is thrown deep inside the service, the code completely skips the rest of the controller.<br>It teleports to a completely different file called GlobalExceptionHandler.</p><p>We call this <strong>“The Teleport”</strong>.</p><p>As a developer reading the controller, we may have no idea what exceptions might occur.<br>We have to globally search the codebase to find where the errors are handled.<br>If someone adds a new exception inside the service, our global handler might catch it as a generic Exception and return a 500 error silently.</p><p>This is what we call <strong>“The Pain”</strong>.</p><p>This breaks the linear flow of your program.</p><blockquote>It forces developers to mentally follow the execution path across multiple files and layers.</blockquote><p>It increases cognitive load massively.<br><strong>We need a way to stop falling through these trapdoors.</strong></p><h3>The Railway Analogy</h3><p>To fix the invisible trapdoor, we need a new mental model.<br>Instead of throwing exceptions, we use <strong>Railway-Oriented Programming (ROP)</strong>.<br>Don’t worry about the fancy functional programming name.<br>Let’s imagine the execution flow as a train track.</p><p><strong>The Main Track (Right)<br></strong>This is our happy path.<br>When everything goes well, the train stays on the main track.<br>It moves forward smoothly to the next station.<br>In our code, we call this the “<strong>Right</strong>” side.</p><p><strong>The Siding (Left)<br></strong>Sometimes, things go wrong.<br>Maybe a user’s wallet has insufficient funds.<br>Maybe a wallet is already suspended.<br>These are expected business failures.<br>When this happens, we don’t derail and crash the train.<br>We <strong>don’t throw an exception</strong> to blow things up.<br>Instead, we gently switch the train to a safe side track.<br>In our code, we call this the “<strong>Left</strong>” side.<br>The train stops safely, and we know exactly why it stopped.</p><p>.</p><p><strong>To see this in action, let’s look at the </strong><strong>freezeForInvestigation flow as a railway system:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PhAfoQFYId_BKhKR" /><figcaption>Railway-Oriented Programming diagram for Freeze for Investigation process showing Happy Path (Right) and Error Sidings (Left)</figcaption></figure><pre>public record Wallet(<br>    String id, String ownerId, BigDecimal balance, Instant lastAccessed, String status) {<br><br>  public Either&lt;DomainError, SuspendedWallet&gt; freezeForInvestigation(<br>      Investigator investigator, AmlAlert alert, ComplianceResponse compliance, int freezeDays) {<br><br>    final var wallet = Wallet.toDomain(this);<br>    final var initialContext = new FreezeContext(investigator, wallet, alert);<br>    final var suspensionEndDate = Instant.now().plus(freezeDays, ChronoUnit.DAYS);<br><br>    // The railway starts here<br>    return authorizeInvestigator(initialContext) // Step 1<br>        .flatMap(this::ensureActiveWallet)       // Step 2<br>        .flatMap(this::ensureUnresolvedAlert)    // Step 3<br>        .map(applyFreezeWith(suspensionEndDate));       // Step 4<br>  }<br><br>  public static WalletDomain toDomain(Wallet wallet) {<br>    return switch (wallet.status()) {<br>      case &quot;PENDING&quot; -&gt; new PendingWallet(<br>          wallet.id(), wallet.ownerId(), wallet.balance(), wallet.lastAccessed());<br>      case &quot;ACTIVE&quot; -&gt; new ActiveWallet(<br>          wallet.id(), wallet.ownerId(), wallet.balance(), wallet.lastAccessed());<br>      case &quot;SUSPENDED&quot; -&gt; new SuspendedWallet(<br>          wallet.id(), wallet.ownerId(), wallet.balance(), wallet.lastAccessed());<br>      default -&gt; throw new InvariantViolationException(<br>          &quot;Wallet status can be only PENDING, ACTIVE, or SUSPENDED!&quot;);<br>    };<br>  }<br>  <br>  private Either&lt;DomainError, FreezeContext&gt; authorizeInvestigator(FreezeContext ctx) {<br>    return ctx.investigator().hasComplianceRole()<br>        ? Either.right(ctx)<br>        : Either.left(new UnauthorizedComplianceRoleException(&quot;Investigator lacks compliance role&quot;));<br>  }<br>  <br>  private Either&lt;DomainError, FreezeContext&gt; ensureActiveWallet(FreezeContext ctx) {<br>    return ctx.wallet() instanceof SuspendedWallet sw<br>        ? Either.left(new WalletAlreadySuspendedException(&quot;Wallet with id %s is already suspended&quot;.(sw.id())))<br>        : Either.right(ctx);<br>  }<br>  <br>  private Either&lt;DomainError, FreezeContext&gt; ensureUnresolvedAlert(FreezeContext ctx) {<br>    final var alert = ctx.alert();<br>    return alert.isResolved()<br>        ? Either.left(new AmlAlertAlreadyResolvedException(&quot;Alert with id %s is already resolved&quot;.formatted(alert.id())))<br>        : Either.right(ctx);<br>  }<br>  <br>  private Function&lt;FreezeContext, SuspendedWallet&gt; applyFreezeWith(Instant expirationDate) {<br>    return ctx -&gt; new SuspendedWallet(<br>        id, balance, ctx.investigator().id(), ctx.alert().id(), expirationDate);<br>  }<br>}</pre><p>This approach makes our code predictable.<br>We can see every possible turn the code can make.<br>There are no hidden trapdoors anymore.</p><h3>‘Either’ as a Legally Binding API Contract</h3><p>Railway-Oriented Programming sounds great in theory.<br>But how do we enforce this train track in Java?<br>We use a generic type called Either&lt;Left, Right&gt;, which is provided by <a href="https://docs.vavr.io/#_either">Vavr.io</a>.</p><p>Many developers think Either is just a fancy wrapper.<br>That is a cute assumption, but it’s completely false. 😏<br>It is a legally binding contract between the provider and the consumer.</p><p><strong>The Provider’s Promise<br></strong>The person writing the function makes a strict promise.<br>They promise <strong>never to throw an exception</strong> for an expected <em>business error</em>.<br>If the investigator is unauthorized, they will not blow up the application.<br>Instead, they will politely wrap that failure in a Left object and return it.<br>They guarantee a safe train ride.</p><p><strong>The Consumer’s Obligation<br></strong>The person calling the function also has an obligation.<br>Because the return type is Either, the Java compiler gets involved.<br>We cannot just grab the Right value and ignore the Left side.</p><blockquote>The type system forces us to unpack the box.<br>We must explicitly declare what to do when it succeeds and what to do when it fails.</blockquote><p><strong>We cannot pretend the error does not exist anymore.</strong></p><p><strong>Let’s look at how the caller (the Controller) handles this new contract:</strong></p><pre>@NullMarked<br>@RestController<br>@RequestMapping(&quot;/wallets&quot;)<br>public class WalletController {<br><br>  @PostMapping(&quot;/{id}/suspensions&quot;)<br>  public ResponseEntity&lt;?&gt; freezeWallet(<br>      @PathVariable(&quot;id&quot;) String walletId,<br>      @RequestBody FreezeWalletRequest request,<br>      @AuthenticationPrincipal Jwt jwt) {<br><br>    // 1. Service now explicitly returns an Either. No hidden trapdoors!<br>    final Either&lt;DomainError, SuspendedWallet&gt; result = <br>        walletApplicationService.freezeForInvestigation(<br>            jwt.getSubject(), walletId, request.amlAlertId());<br><br>    // 2. We handle both paths explicitly using .fold()<br>    return result.fold(<br>        this::toErrorResponse,     // Left track (Failure)<br>        ResponseEntity::ok         // Right track (Success)<br>    );<br>  }<br><br>  // 3. Pattern Matching (Java 21+) to map Domain Error to HTTP Status<br>  private ResponseEntity&lt;ProblemDetail&gt; toErrorResponse(DomainError error) {<br>      <br>    final HttpStatus status = switch (error) {<br>      case ResourceNotFound _ -&gt; HttpStatus.NOT_FOUND;<br>      case BusinessRuleViolation _ -&gt; HttpStatus.UNPROCESSABLE_ENTITY;<br>      case ConcurrencyConflict _ -&gt; HttpStatus.CONFLICT;<br>      default -&gt; HttpStatus.INTERNAL_SERVER_ERROR;<br>    };<br><br>    final var detail = ProblemDetail.forStatusAndDetail(status, error.getMessage());<br>    return ResponseEntity.status(status).body(detail);<br>  }<br>}</pre><p>Notice that the Controller no longer throws any domain exceptions.<br>There is no GlobalExceptionHandler intercepting our business logic.<br>There is no teleportation across files.<br>We use Java 21’s pattern matching to map the specific DomainError to the correct HTTP Status.</p><p><strong>The Shield Against Data Corruption<br></strong>But what about real, unexpected <em>system errors</em> or <em>domain invariant violations</em>?<br>Imagine someone arbitrarily runs an INSERT orUPDATE script in the database (on development env 🙂).<br>They accidentally set a wallet balance to a negative number.<br>But our domain model strictly forbids negative numbers.<br>Or maybe they insert a random string into a wallet status column.<br>When our domain model loads this data, it violates a strict business invariant.<br> In this specific case, the provider does <strong>NOT</strong> return a Left.<br> The provider must fail fast and throw a InvariantViolationException or IllegalStateException immediately.</p><p><strong>Why?</strong><br><strong>Because dead programs tell no lies.</strong><br>If the data is corrupted, we must crash the process right there.<br>We don’t want corrupted data to flow further into our business logic.</p><blockquote>Crashing immediately stops the bleeding and gives us a clear stack trace to fix the root cause.</blockquote><h3>Functional Core, Imperative Shell: Where Does the Contract Live?</h3><p>Now we know how the Either contract works.<br>But where exactly do we use it?<br>If we use Either everywhere, our codebase will become a heavy, unreadable mess.<br>This is where the &quot;<strong>Functional Core, Imperative Shell</strong>&quot; architecture comes in.</p><h4>The Functional Core</h4><p>This is our Domain Model, just like the Wallet aggregate in our project.<br>It contains our most valuable business logic.<br><strong>In this core, the code must be 100% pure.<br></strong>There is absolutely no try-catch block allowed here.<br>There are no database queries, no HTTP calls, and no message broker publications.<br>If a business rule fails, the core simply returns an Either.Left.<br>For example, a withdrawal with insufficient funds returns a Left(WalletBalanceInsufficientException).<br>The core never throws an exception for business rules.<br>It just calculates the new state, generates domain events, and returns the data.</p><h4>The Imperative Shell</h4><p>This is the boundary of our system.<br>This is where our application interacts with the dirty, unpredictable outside world.<br><strong>In the shell, using </strong><strong>try-catch, I/O connis fine and necessary.</strong></p><p><strong>The Orchestrator (Application Service)<br></strong>This is where we glue the pure core and the dirty shell together.<br>We use the <strong>Read-Decide-Write</strong> pattern inside our application service.<br>Let’s look at how we implemented this for our freeze wallet use case:</p><pre>// In WalletApplicationService.java<br>@NullMarked<br>@Transactional<br>public Either&lt;DomainError, SuspendedWallet&gt; freezeForInvestigation(<br>    String investigatorId, String walletId, String alertId) {<br><br>  // ==========================================<br>  // 🐚 1. READ (Imperative Shell)<br>  // ==========================================<br>  // We allow I/O operations here.<br>  // System exceptions bubble up naturally if the database is down.<br>  final var investigator = investigatorRepository.findById(investigatorId).orElseThrow();<br>  final var wallet = walletRepository.findById(walletId).orElseThrow();<br>  final var alert = amlAlertRepository.findById(alertId).orElseThrow();<br>  <br>  // External HTTP calls are perfectly fine here.<br>  // We gather all dirty data first before calling our pure logic.<br>  final var complianceRes = complianceHttpClient.verifyAmlStatus(alert.id(), investigator.id());<br><br>  // ==========================================<br>  // 🎯 2. DECIDE (Functional Core)<br>  // ==========================================<br>  // We pass the clean data into our pure domain model.<br>  // This Railway chain is 100% pure.<br>  // It only returns a new object in memory.<br>  // It does NOT save anything to the database yet.<br>  final Either&lt;DomainError, SuspendedWallet&gt; result =<br>      wallet.freezeForInvestigation(investigator, alert, complianceRes);<br><br>  // ==========================================<br>  // 🐚 3. WRITE (Imperative Shell)<br>  // ==========================================<br>  // We perform side effects on each track of the railway, then return decision unchanged.<br>  // Success track: commit the freeze (persist, audit, announce).<br>  // Failure track: audit the rejected freeze.<br>  return result<br>      .peek(commitFreezeOrderedBy(investigatorId))<br>      .peekLeft(recordRejectedFreezeOf(walletId, investigatorId));<br>}<br><br>// ===== Side-effect helper methods =====<br>private Consumer&lt;SuspendedWallet&gt; commitFreezeOrderedBy(String investigatorId) {<br>  return suspendedWallet -&gt; {<br>      walletRepository.save(Wallet.toEntity(suspendedWallet));<br>      auditLogger.logFreezeAttempt(suspendedWallet.id(), investigatorId, &quot;SUCCESS&quot;);<br>      eventPublisher.publishEvent(new WalletFrozenEvent(suspendedWallet.id(), suspendedWallet.ownerId()));<br>  };<br>}<br><br>private Consumer&lt;DomainError&gt; recordRejectedFreezeOf(String walletId, String investigatorId) {<br>  return error -&gt; auditLogger.logFreezeAttempt(walletId, investigatorId, &quot;REJECTED: &quot; + error.getMessage());<br>}</pre><p>Notice how we gather all the necessary data from the database and external HTTP clients <strong>before</strong> starting the Railway chain.<br>This is the <strong>Read-Decide-Write</strong> pattern.<br>We keep the I/O in the Imperative Shell, and pass only pure data into our Functional Core chain.</p><p><strong>For incoming traffic (Rest Controller / Functional Endpoints)<br></strong>We might use a standard @RestController.<br>If we do, we can use @RestControllerAdvice to catch system exceptions and translate them globally.<br>However, in our modern setup, we use Spring Functional Endpoints (RouterFunction and Handler).</p><p>In this boundary layer, we unwrap the Either received from the core.</p><ul><li>If it is a Right, we map the data to an HTTP <strong>200 OK</strong> or <strong>201 Created</strong></li><li>If it is a Left, we convert the domain error into an <strong>HTTP 400 Bad Request</strong>, <strong>409 Conflict,</strong> <strong>422 Unprocessable Entity</strong>, etc.</li><li>If a <strong><em>system error</em></strong> or an <strong><em>invariant violation</em></strong> occurs, it safely bubbles up and becomes an <strong>HTTP 5xx Server Error</strong></li></ul><p><strong>For external integrations<br></strong>We have Database Adapters and Message Publishers (like Kafka or RabbitMQ).<br>These adapters talk to external libraries that constantly throw unpredictable exceptions.<br>Sometimes, an adapter must catch a specific external exception and translate it into a Domain Error.<br>It must do this translation <em>before</em> passing the data into the pure Functional Core.</p><blockquote><em>The rule is simple:<br>Keep the core pure with Either, and let the shell handle the dirty exceptions.</em></blockquote><h3>Wrap-Up: The Foundation is Set</h3><p>In this first part, we dismantled the traditional way of handling errors.<br>We learned that throwing exceptions creates invisible trapdoors in our business logic.<br>We explored how to <strong>replace those trapdoors</strong> with a strict API contract using Either.<br>We discussed how to <strong>separate our pure Domain Model from the dirty external world</strong> using the Functional Core and Imperative Shell architecture.</p><p>However, getting a new mindset does not mean we can change our architecture overnight.<br>It takes time, intense communication, and continuous knowledge sharing.<br>We have to repeatedly speak the same ubiquitous language and shared team vocabulary.<br>We have to talk about Either, Left, and Right until it becomes normal.</p><p>But remember, we may not need to understand complex Functional Programming category theory. (But nice to understand)</p><p>We must constantly encourage Functional Programming discipline within our team.<br>Everyone must see exactly why predictable state transitions are always better than unpredictable mutable states.</p><p>In <strong>Part 2</strong>, we will get our hands dirty with real implementation details to support this cultural shift.<br><strong>We will explore how to implement this pattern across different Java eras:</strong></p><ul><li>Using switch expressions in <strong>Java 21/24</strong></li><li>Leveraging Record and instanceof in <strong>Java 17</strong></li><li>Building a lifeline for <strong>Java 8</strong> using Vavr’s pattern matching</li></ul><p>We will also discuss the critical trade-offs.<br>We must know exactly when to use Either and when to fail fast with a real exception.<br>Most importantly, we will dive deep into how this architectural shift transforms our team culture.</p><blockquote>A strict technical contract forces better human communication.</blockquote><p>We will explore how this approach shifts our code review discussions.<br>And I believe this is exactly what makes critical incident war rooms significantly less stressful.</p><p>Stay tuned for Part 2, and let’s elevate our engineering culture together!</p><p><strong>The Contract of Failure in Java Series:</strong></p><ul><li><strong><em>Part 1: Rethinking Exceptions with </em></strong><strong><em>Either in Java (You are here)</em></strong></li><li><em>Part 2: Pattern Matching in Java, Trade-offs, and Team Culture (Coming soon)</em></li></ul><p><strong>Thanks for reading until the final line.<br>If you’re confused or have any questions, don’t hesitate to leave comments.</strong></p><p>Feel free to connect with me and stay updated on the latest insights and discussions.<br>👉 <a href="https://linkedin.com/in/fResult">https://linkedin.com/in/fResult</a> 👈</p><p>And happy coding 🫰🫰</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=75f8a4e89486" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[Tips] Fix .env File Not Loaded in IntelliJ Tests (Gradle & Colima)]]></title>
            <link>https://fresult.medium.com/tips-fix-env-file-not-loaded-in-intellij-tests-gradle-colima-b5beaae0a96d?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/b5beaae0a96d</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[testcontainer]]></category>
            <category><![CDATA[intellij-idea]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Sun, 15 Feb 2026 09:06:40 GMT</pubDate>
            <atom:updated>2026-06-13T09:18:38.117Z</atom:updated>
            <content:encoded><![CDATA[<h3>[Tips] Fix .env File Not Loaded in IntelliJ Tests (Gradle &amp; Colima)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7XIY3h5f9P1U5B1Z" /><figcaption>Photo by <a href="https://unsplash.com/@howei?utm_source=medium&amp;utm_medium=referral">Howei Wang</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I recently set up <strong>Testcontainers</strong> and <strong>Colima</strong> in my <a href="https://github.com/fResult/resilience-wallet-ledger">Spring Boot project</a>.<br>I use <strong>Colima</strong> simply because it is a lightweight, open-source alternative to Docker Desktop.</p><p>I attached my .env file using the <strong>EnvFile</strong> plugin in the Run/Debug Configurations.</p><p>(I followed the official guide here: <a href="https://java.testcontainers.org/supported_docker_environment/#colima">https://java.testcontainers.org/supported_docker_environment/#colima</a>)</p><pre>colima start --network-address<br>export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock<br>export TESTCONTAINERS_HOST_OVERRIDE=$(colima ls -j | jq -r &#39;.address&#39;)<br>export DOCKER_HOST=&quot;unix://${HOME}/.colima/default/docker.sock&quot;</pre><p>It looked correct.<br>But when I ran the test, it failed. 😤 😤 😤</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gRwNws3DiRi5JXKHsiV-MA.png" /><figcaption>Screenshot of IntelliJ IDEA Run/Debug Configurations showing the EnvFile plugin enabled with a .env file attached, but the test result on the left shows a failure</figcaption></figure><p><strong>The Error:</strong></p><pre>.<br>... Leave for brevity ...<br>.<br><br>2026-02-15T13:25:20.993+07:00 ERROR 20954 --- [Resilience Wallet Ledger] [    Test worker] tc.testcontainers/ryuk:0.13.0            : Could not start container  <br>  <br>com.github.dockerjava.api.exception.NotFoundException: Status 404: {&quot;message&quot;:&quot;No such container: ...&quot;}  <br> at org.testcontainers.shaded.com.github.dockerjava.core.DefaultInvocationBuilder.execute(DefaultInvocationBuilder.java:241)<br> ...<br><br>.<br>... Leave for brevity ...<br>.</pre><p>It seemed like the test process completely ignored the environment variables I just attached.<br>This happens because IntelliJ defaults to using <strong>Gradle</strong> as the test runner. <br>Gradle runs in a separate process (daemon) and doesn’t always inherit the EnvFile configuration.</p><h3>Solutions</h3><p>Here are the 2 solutions I found.</p><ol><li><strong>The “Quick &amp; Risky” Way:</strong> Check the box Enable experimental integrations (e.g. Gradle) <br>(⚠️ <strong>Warning:</strong> Use with caution. It is experimental and <strong>may break at any time</strong>.)</li><li><strong>The “Stable” Way:</strong> Change the test runner from Gradle to IntelliJ IDEA</li></ol><blockquote><strong><em>Note:</em></strong><br>I avoided <strong>disabling Ryuk</strong>.<br>Ryuk is responsible for cleaning up containers.<br>Disabling it is just a workaround, and it will leave “zombie” containers running on our machine, consuming memory.</blockquote><h3>Option 1: Enable Experimental Integrations</h3><p>If you want to keep using Gradle as the runner, there is a specific setting for this.</p><p>In your Run/Debug Configuration (inside the <strong>EnvFile</strong> tab), look at the right side.<br><strong>You will see a checkbox:</strong></p><pre>Enable experimental integrations (e.g. Gradle) — may break any time!</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/831/1*LMEXFOoBS7xNsKSkfNtXtA.png" /><figcaption>Screenshot of the EnvFile tab in IntelliJ Run/Debug Configurations, highlighting the ‘Enable experimental integrations (e.g. Gradle)’ checkbox</figcaption></figure><p>After checking this box, the test passed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4vuAlA26kWIJe89PJ6mCaA.png" /><figcaption>Screenshot of Running Test with Testcontainers passed for Gradle Default</figcaption></figure><blockquote><strong><em>My thought:</em><br></strong>It works, <strong>but</strong> the warning <strong>“may break any time”</strong> makes me nervous.<br>I don’t want to come back and fix this again in the future.</blockquote><blockquote>So, I prefer Option 2.</blockquote><h3>Option 2: Switch Test Runner to IntelliJ (Recommended)</h3><p>This is a more permanent fix.<br>By switching to the <strong>IntelliJ Native Runner</strong>, the IDE handles the process directly.<br>It picks up the EnvFile plugin configuration 100% of the time.</p><p><strong>How to do it:</strong></p><ol><li>Open Settings (or Preferences)</li><li>Go to Build, Execution, Deployment &gt; Build Tools &gt; Gradle</li><li>Change Run tests using from Gradle to IntelliJ IDEA</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GiYtjZKubKwNGhFqk2h9Kg.png" /><figcaption>Screenshot of IntelliJ Settings under Build Tools &gt; Gradle, showing the ‘Run tests using’ dropdown changed from ‘Gradle’ to ‘IntelliJ IDEA’</figcaption></figure><p><strong>That’s it!</strong><br>No more missing variables.<br>The tests just work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wloGYzxt1lpmv31iwDZRAA.png" /><figcaption>Screenshot of the Run dashboard in IntelliJ showing the WalletIT integration test passing successfully</figcaption></figure><p>I originally documented this solution in the <strong>README</strong> of my practice project, <strong>Resilience Wallet Ledger</strong>.<br>If you want to see how this configuration fits into a complete Spring Boot application, you can check the full setup here:</p><p>👉 <a href="https://github.com/fResult/resilience-wallet-ledger?tab=readme-ov-file#-running-tests"><strong>GitHub: Resilience Wallet Ledger — Running Tests</strong></a></p><p>Happy Coding! 🚀</p><p><strong>Thanks for reading until the final line.<br>If you’re confused or have any questions, don’t hesitate to leave the comments.</strong></p><p>Feel free to connect with me and stay updated on the latest insights and discussions.<br>👉 <a href="https://linkedin.com/in/fResult">https://linkedin.com/in/fResult</a> 👈</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b5beaae0a96d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ทำไมแค่ “โค้ดรันผ่าน” ไม่ใช่การแก้ปัญหา? บทเรียนเปลี่ยนชีวิตจาก Maths for Working Programmers]]></title>
            <link>https://fresult.medium.com/%E0%B8%97%E0%B8%B3%E0%B9%84%E0%B8%A1%E0%B9%81%E0%B8%84%E0%B9%88-%E0%B9%82%E0%B8%84%E0%B9%89%E0%B8%94%E0%B8%A3%E0%B8%B1%E0%B8%99%E0%B8%9C%E0%B9%88%E0%B8%B2%E0%B8%99-%E0%B9%84%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%8A%E0%B9%88%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B9%81%E0%B8%81%E0%B9%89%E0%B8%9B%E0%B8%B1%E0%B8%8D%E0%B8%AB%E0%B8%B2-%E0%B8%9A%E0%B8%97%E0%B9%80%E0%B8%A3%E0%B8%B5%E0%B8%A2%E0%B8%99%E0%B9%80%E0%B8%9B%E0%B8%A5%E0%B8%B5%E0%B9%88%E0%B8%A2%E0%B8%99%E0%B8%8A%E0%B8%B5%E0%B8%A7%E0%B8%B4%E0%B8%95%E0%B8%88%E0%B8%B2%E0%B8%81-math-for-working-programmers-5775f3772000?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/5775f3772000</guid>
            <category><![CDATA[self-improvement]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[maths-for-programmers]]></category>
            <category><![CDATA[problem-solving]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 08:23:14 GMT</pubDate>
            <atom:updated>2026-10-12T17:18:43.294Z</atom:updated>
            <content:encoded><![CDATA[<h3>ทำไมการเขียนโค้ดให้รันผ่าน ถึงยังไม่ใช่การแก้ปัญหา? (บทเรียนจาก Mathematics for Working Programmers)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*U5gluKiFlQ9t65e7" /><figcaption>ขอขโมยภาพประกาศคลาสของพี่เดฟ มาเป็น Cover ของบทความฮะ 😃</figcaption></figure><p>ผมดีใจที่เห็นพี่เดฟ (<a href="https://www.facebook.com/rawitat">Dave Rawitat Pulam</a>) ยังประกาศสอนคลาส <strong>Mathematics for Working Programmers</strong> อยู่เรื่อยๆ<br>เพราะคลาสนี้คือหนึ่งในจุดเปลี่ยนสำคัญในชีวิตการทำงานของผมเลย<br>(ต่อไปนี้ผมจะย่อชื่อคลาสเหลือแค่คำว่า <strong>“M4WP”</strong> นะครับ)</p><p><strong>ชีวิตการทำงานของผมมี “จุดเปลี่ยน” ใหญ่ๆ อยู่สองครั้ง</strong></p><p><strong>ครั้งแรก</strong> คือการได้เจอพี่ชายท่านหนึ่งเมื่อหลายปีก่อน<br>ที่เปลี่ยนเด็ก (ที่มีอายุเยอะนิดหน่อย(?)) ที่เกลียดการเขียนโปรแกรมชนิดเข้าไส้<br>ให้กลายมาเป็นคนที่อยากจะเป็นโปรแกรมเมอร์ให้ได้<br>นั่นคือก้าวแรกที่สำคัญมาก…</p><p><strong>ครั้งที่สอง</strong> คือการได้มาเรียนกับพี่เดฟ คลาสนี้ไม่ใช่คลาสที่สอนเขียนโค้ด<br>และ <strong>ไม่ใช่คณิตศาสตร์แบบที่เราท่องจำกันมา</strong> ไม่ใช่เรื่องการนั่งบวกลบคูณหาร<br>การถอดรูท หรือ มานั่งดิฟ/อินทิเกรต เพื่อหาคำตอบเป็นตัวเลข<br>แต่มันคือการพาไปเห็น <strong>“โครงสร้างของความเป็นเหตุเป็นผล”</strong> (Logical Structure) ที่ซ่อนอยู่เบื้องหลังทุกสิ่ง</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w7i6IdaRwXCnEg1s7t9m2A.png" /><figcaption>ตัวอย่าง slide สวยๆ จาก M4WP Day 4 (หรือ Day 3 นี่แหละ ไม่แน่ใจ 🫤)</figcaption></figure><p>ความเจ๋งคือ พี่เดฟแกใช้วิธี <strong>“เขียนกระดานดำ” (ด้วยชอล์ก!) สดๆ</strong> (แทบไม่เปิดสไลด์เลยถ้าไม่จำเป็น)<br>เพื่อพาเราค่อยๆ <strong>“ประกอบร่าง”</strong> องค์ความรู้ขึ้นมาทีละชิ้น<br>ผ่านบทสนทนาและการถกเถียงกันในห้องเรียนจนตกผลึก<br><em>(ส่วนสไลด์สวยๆ แกเตรียมไว้ให้เราเอาไปทบทวน<br>และเป็นลายแทงให้ไปค้นคว้าเพิ่มเติมเท่าที่ต้องการเลยฮะ ไม่ต้องห่วง)</em></p><p><strong>การได้เห็น “โครงสร้าง” ที่ค่อยๆ ถูกกางออกมาให้เห็นกับตาตรงหน้านี่แหละ<br></strong>ที่ทำให้ผมต้องอ้าปากค้าง ตบโต๊ะดังฉาด! และตาสว่างซ้ำแล้วซ้ำเล่า…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/497/0*V7fivPlS1rmCBrF8.gif" /><figcaption>When you finally see the structure behind the code</figcaption></figure><p>เห้ย… การสร้างซอฟต์แวร์ การแก้ปัญหาจริงๆ มันควรเป็นแบบนี้ดิ….<br>ไม่ใช่การเขียนโค้ดตามๆ กันมา หรือสักแต่ว่าเขียนให้มันรันผ่าน</p><p>…นั่นคือสิ่งที่ผมคิดหลายต่อหลายครั้งในระหว่างที่เรียนคลาสของพี่เดฟเป็นครั้งแรก</p><p>แม้ว่าในตอนนั้น ผมจะยังมืดแปดด้าน ผมมองไม่เห็นทางเลยว่า<br>จะหยิบจับทฤษฎีพวกนี้ไปแปลงเป็น Code ในงานจริงได้ยังไงในทันที<br>แต่สัญชาตญาณลึกๆ ของผมมันตะโกนบอกว่า <strong>“นี่แหละคือทางที่ถูกต้อง”</strong></p><p>มันคือการ <strong>“บ่มเพาะเมล็ดพันธุ์ทางความคิด”<br></strong>ที่ผมรู้ตัวทันทีเลยว่า ต้องใช้เวลาฟูมฟักอีกหลายปี<br>กว่าที่มันจะเติบโตและผลิดอกออกผล<br>ให้ผมกลายเป็น Programmer ที่เข้าใจแก่นแท้ของระบบจริงๆ</p><blockquote>ไม่ใช่แค่ช่างก่ออิฐที่ฉาบปูนไปวันๆ</blockquote><h3>นิยามของ “Problem Solving”</h3><p>สิ่งที่ Impact ผมที่สุด คือ<strong>นิยามของคำว่า “Problem Solving” </strong>หรือ <strong>การแก้ปัญหา</strong></p><p>เมื่อก่อน เวลาเจอ Bug หรือโจทย์ยากๆ ผมมักจะแก้ปัญหาแบบ <strong>“มวยวัด”</strong><br>หรือก็คือการแก้เฉพาะหน้าไปเรื่อยๆ<br>“ก็แค่” ใส่ Log ดู State ทีละบรรทัด<br>แล้วแก้จนกว่าจะได้ผลลัพธ์ที่ (คิดว่า) ถูก<br>ทำแบบนี้วน Loop ไปเรื่อยๆ<br>“เหมือนเอาเทปกาวมาแปะรูรั่ว”</p><p>แต่พี่เดฟพาไปรู้จักนิยามที่เรียบง่ายแต่ทรงพลังที่สุด (ซึ่งแกบอกว่าได้มาจากหนังสือธุรกิจของญี่ปุ่นเล่มหนึ่ง):</p><blockquote>“การแก้ปัญหา คือการพาตัวเองจาก Current State (A) ไปสู่ Desired State (B)”</blockquote><p>หรือพูดง่ายๆ ก็คือ <strong>“การปิด Gap จากจุด A ไปยังจุด B”</strong> นั่นเอง</p><p>แค่นั้นเลยครับบ!</p><blockquote>พูดถึงเรื่องนี้ ผมนึกถึงบทความของเพื่อนคนนึง (<a href="https://medium.com/u/66d458719742">Rachata.Ptnn</a>)<br>ที่ไปเคยเรียนคลาสเดียวกันมา<br>เขาเขียนอธิบายเรื่อง <strong>“Problem-Solving, Sub-Problem และ Algorithm”<br></strong>พร้อมยกตัวอย่างเรื่องการลดน้ำหนักไว้ได้เห็นภาพและเข้าใจง่ายมาก</blockquote><blockquote>ถ้าใครอยากเจาะลึกว่าไอ้การแตก Problem A → B <br>ออกเป็น Sub-problems ย่อยๆ มันทำยังไง<br>(และทำไมมันถึงช่วยให้เราแก้โจทย์ที่ซับซ้อนได้)<br>ผมแนะนำให้ตามไปอ่านบทความของเขาต่อได้ฮะ<br><strong>ของเขาดีมากจริงๆ ฮะ<br><br><em>👇🏻 จิ้ม link ข้างล่างเบาๆ ได้เบย👇🏻</em></strong></blockquote><p><a href="https://medium.com/@rachata.ptnn/problem-solving-sub-problem-%E0%B9%81%E0%B8%A5%E0%B8%B0-algorithm-%E0%B8%88%E0%B8%B2%E0%B8%81%E0%B8%A1%E0%B8%B8%E0%B8%A1%E0%B8%A1%E0%B8%AD%E0%B8%87-software-developer-6e5f3a484505">Problem-Solving, Sub-Problem และ Algorithm</a></p><p>แล้วไงต่อ…? 🤨🧐</p><p>คุณรู้สึกว่าฟังดูเหมือนกำปั้นทุบดินใช่ไหมนะ?<br>แต่ในโลกของ Software Engineering … นี่คือแก่นของทุกอย่าง</p><p>มันเปลี่ยนวิธีคิดผมจากการเขียนคำสั่งสั่งคอมพิวเตอร์ทีละบรรทัด (Imperative)<br>ให้กลายเป็นการมองหา <strong>“Function” ที่เชื่อมโยงระหว่าง A ไป B</strong> อย่างตั้งใจ (Equational Reasoning)</p><p>แทนที่จะเขียนโค้ดแบบเดาทางไปเรื่อยๆ<br>ผมเริ่มเปลี่ยนวิธีทำงาน… จากที่เคยเริ่มด้วยการพิมพ์โค้ด<br>ผมเริ่มจากการ <strong>“ออกแบบโครงสร้าง” (Structure &amp; Modeling)</strong> ก่อน</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eEohIy89RBTgQbgsFEEmIg.png" /><figcaption>ตัวอย่าง slide สวยๆ จาก M4WP Day 6</figcaption></figure><p>พยายามมองให้ออกว่า Domain ของเรามีหน้าตาเป็นยังไง<br>มี State อะไรบ้างที่เป็นไปได้ (และเป็นไปไม่ได้)</p><p>แล้วค่อยลงมือเขียนโค้ดโดยเน้นการ “พิสูจน์ความถูกต้อง” ในระดับ Logic<br>ให้ได้มากที่สุดเท่าที่จะทำได้ แม้จะยังทำได้ไม่ Perfect 100%<br>แต่มันทำให้ผมไม่ต้องคอยลุ้น หรือ Log ดูหน้างานพร่ำเพรื่อแบบเมื่อก่อน<br>เพราะเราได้ออกแบบ Logic และ Structure ให้มัน Make Sense ตั้งแต่ก่อนจะเริ่มพิมพ์โค้ดบรรทัดแรกแล้ว</p><h3>“เรียน 1 เดือน ย่อย 1 ปี” โคตรไม่เกินจริง</h3><p><strong>เคยมีผู้เรียนคนหนึ่งกล่าวไว้ในคลาสออนไลน์คลาสนึงที่พี่เดฟเป็นผู้สอนไว้ว่า</strong></p><blockquote><strong>“เรียน 1 เดือน แล้วต้องย่อยอีก 1 ปี”</strong></blockquote><p>ประโยคนี้แม่งไม่เกินจริงเลยฮะ (ผมเห็นข้อความนั้นแล้วยังกดปุ่ม Emoji ขำ (🤣) ใส่อยู่เลย) … โคตรโดนใจจ๊อดด</p><p><strong>ต้องบอกก่อนว่า M4WP เนี่ย มันเป็น Series ยาวนะ</strong><br>(แบ่งเป็น Episode ละ 2 วัน มีทั้ง Day 0, 1–2, 3–4 ไปยัน Day 7–8)</p><p>แต่เอาจริงๆ… แค่ Episode เดียวที่เรียนกัน 2 วันเต็มๆ เนี่ย<br>ไอ้เนื้อหาที่ดูเหมือนโคตร Abstract (อย่างพวก Set, Logic, Category Theory)<br>แม่งเล่นเอาคนพื้นฐานไม่แน่นอย่างผม ถึงกับสมองน็อกเป็นปลาน็อกน้ำ<br>หลับกลางอากาศไปในช่วงบ่ายเลยทุกครั้ง 🤣<br>(ผมเป็นเด็กสายอาชีพเก่า ที่คณิตศาสตร์ลากเลือดมาตลอด)</p><p>แต่เชื่อมั้ยว่า… ไอ้สิ่งที่อัดเข้าไปตอนนั้น…</p><blockquote>“พวกมันคือยาขมที่ค่อยๆ ออกฤทธิ์”</blockquote><p>มันต้องใช้เวลาค่อยๆ ย่อย ค่อยๆ ปรับจูนสมองที่เคยชินกับการ “เขียนโค้ดให้เสร็จๆ ไป” ให้กลายมาเป็นสมองที่ “มองหาโครงสร้างและความเป็นเหตุเป็นผล”</p><p>โดยส่วนตัวแล้ว แต่ละ Episode ที่เรียนไป ผมใช้เวลาย่อยอย่างน้อย 3 เดือน<br><em>(ยกเว้น Day 0 ที่ยังเป็น Intro ปูพื้นฐานนะ<br>อันนั้นยังพอเข้าใจได้ง่ายหน่อย แต่หลังจากนั้นคือของจริง…)</em><br>และผมประมาณเอาไว้ว่ายังเข้าใจไม่ถึง 20% ของสิ่งที่ได้จากการเรียน M4WP ด้วย<br>แต่แค่นี้ผมก็รู้สึกว่าคุ้มค่าที่ลงทุนเวลาไปกับสิ่งนี้แล้วล่ะ</p><p>ผมเคยคิดเล่นๆ ว่า ถ้าชาตินี้ผมพยายามจนเก็บเกี่ยวความรู้ได้สัก 15-20% ของแก<br>สำหรับผม… นั่นคือการเติบโตที่ผมภูมิใจที่สุดในฐานะ Programmer คนนึงแล้ว<br>(และผมจะพยายามไปให้ถึงจุดนั้นให้ได้ฮะ 😉)</p><h3>สิ่งที่ AI ให้ไม่ได้… คือ “รากฐาน” ของวิธีคิด</h3><p>ทุกวันนี้ แม้เราจะอยู่ในยุคที่คนเก่งๆ เขาใช้ AI ช่วย Generate Code ได้ภายในไม่กี่นาที<br>แต่ “รากฐาน” ทางความคิดเหล่านี้ คือสิ่งที่ AI ให้ไม่ได้</p><p>มันทำให้ผมกลายเป็นคนที่ “แก้ปัญหาอย่างเป็นระบบมากขึ้น”<br>โค้ดที่ออกมาจึงคาดเดาได้, และสะท้อน Logic ของโลกความเป็นจริง<br>และเป็นโค้ดที่สามารถใช้สื่อสารกับ business person ได้เข้าใจง่าย<br>แทนที่จะเป็นโค้ดที่เต็มไปด้วย if-else, try-catch ซ้อนกันยุ่บยั่บจนอ่านไม่รู้เรื่อง</p><p><strong>พอนึกถึงคำว่า “รากฐาน” ภาพเหตุการณ์นึงในอดีตก็แว้บเข้ามาในหัวผมทันที…</strong></p><p>ย้อนกลับไปช่วงแรกๆ ที่ผมเริ่มหัดเขียนโปรแกรม<br>ผมมีโอกาสได้ไปอาศัยอยู่กับครอบครัวของพี่ชายท่านแรกที่ผมเอ่ยถึง<br>พี่เขาหวังดี อยากปั้นให้ผมเป็นลูกมือช่วยงานแก<br>แกเคี่ยวเข็ญผมหนักมาก… เรียกว่า <strong>“นรกแตก”</strong> เลยก็ว่าได้</p><p>ผมนั่งฝึกเขียนโค้ดวันละ 10–14 ชั่วโมง ทุกวัน!<br>ติดต่อกันร่วม 3 เดือน แต่ด้วยความที่หัวช้า (และพื้นฐานเป็นศูนย์)<br>แค่ Loop ง่ายๆ ผมยังไม่เข้าใจ logic ของมันเลย<br>เขียนโค้ดทีไรก็โดนพี่เขาดุประจำ (บางทีก็เขกมะเหงกให้ด้วย) ประมาณว่า</p><p><strong><em>“มึงชอบใช้แต่ทางลัด ชอบท่องจำ แต่ไม่ยอมทำความเข้าใจ”</em></strong></p><p>สุดท้าย… ผมไปต่อไม่ไหว สมองผมรับ Load ขนาดนั้นไม่ได้<br>จนต้องเก็บข้าวของระเห็จออกจากบ้านแกมา</p><blockquote><strong><em>ออกตัวไว้ก่อน</em></strong><br>ที่จริงพี่เขาใจดีนะ เดี๋ยวหลายคนอ่านแล้วเข้าใจผิดว่าทำไมโหดร้ายกันขนาดนี้ 🥹<br>เขาสอนให้ทั้งการเขียนโปรแกรมทั้งเรื่องการใช้ชีวิต</blockquote><blockquote>ทั้งยังได้ซึมซับแนวคิดเรื่อง FP จากแกมาเป็นต้นทุนช่วยในหลายปีถัดมา<br>โดยที่แกไม่เคยพูดคำว่า FP หรือ Jargon ของมันเลยแม้แต่คำเดียว!</blockquote><p>แต่ในช่วงเวลาที่อยู่ที่นั่น นอกจากเรียนโค้ด<br>ผมก็ต้องช่วยงานบ้านแลกข้าวแลกน้ำ กวาดบ้าน ล้างจาน<br>ไปจนถึงช่วยงานใช้แรงอย่างการทำสวน</p><p>มีอยู่วันหนึ่ง ในช่วงบ่ายที่อากาศกำลังสบาย (แต่เหงื่อผมท่วมตัว)<br>ผมกำลังช่วยแกขุดรากต้นไผ่ไปทิ้งจนเสร็จ…<br>พี่เขาชวนผมเดินพักเหนื่อยคุยกันในหมู่บ้าน<br>บทสนทนาวันนั้นดูเรียบง่าย<br>แต่มีประโยคหนึ่งที่แกย้ำกับผม (เป็นครั้งที่ร้อย) ว่า…</p><blockquote><strong><em>“มึงจำไว้นะ… ทำอะไรพยายามอย่าใช้แต่ทางลัด<br>ถ้าบ้านมึงตอกเสาเข็มวางรากฐานไม่ดี<br>วันนึงบ้านมึงจะพังทลายลงมาทั้งหลัง”</em></strong></blockquote><p>ตอนนั้นผมฟังแล้วก็พยักหน้าช้าๆ ว่าเข้าใจ แต่ในใจแอบคิดไปว่า…</p><p><strong>“รอดแล้วตรู… โชคดีนะเนี่ยที่ตรูอาศัยนอนใต้สะพานลอย ถึงบ้านพังก็ไม่ทับหัว” 🤣</strong></p><p>ตัดภาพกลับมาวันนี้… หลังจากผ่านคลาสของพี่เดฟ<br>และผ่านประสบการณ์ทำงานด้าน Software มาพักใหญ่<br>ผมเข้าใจแล้วครับว่า ทั้งพี่เดฟ และพี่ชายท่านนั้น<br>พวกเขากำลังสอนสิ่งเดียวกัน ซึ่งก็คือ</p><blockquote><strong>“ความเชื่อมั่นในการสร้างรากฐานที่แข็งแกร่ง”</strong></blockquote><p>ในวันที่เราต้องสร้างตึกระฟ้า (Software ที่ซับซ้อน)<br>ทางลัดอาจจะพาเราไปถึงชั้น 2, 3, …, 5, …, 10 ได้ไว</p><blockquote><strong>แต่ทางลัด…ไม่มีทางพาเราไปถึงชั้นที่ 100<br>โดยที่ตึกไม่ถล่มลงมา</strong></blockquote><p>ใครที่ทำงานสายนี้ และรู้สึกว่าตัวเองกำลัง <strong>“ตัน”</strong><br>หรือรู้สึกว่าโค้ดที่เขียน (ด้วย AI) มัน <strong>“Fragile”</strong> (เปราะบาง) เหลือเกิน<br>ลองมาเรียนคลาสนี้กันดูครับ (<a href="https://github.com/rawitat/class-reviews">Mathematics for Working Programmers</a>)<br>หรือติดตามพี่เดฟ (<a href="https://www.facebook.com/rawitat">Dave Rawitat Pulam</a>) ได้บน Facebook ฮะ</p><blockquote>ผมเชื่อว่าคุณจะมองเห็นโลกของ Software ไม่เหมือนเดิมอีกเลย</blockquote><p>.</p><p>ปล. ผมเริ่มเรียนคลาสนี้ (รวมถึงคลาสอื่นๆ) ของพี่เดฟมาสักพักใหญ่ๆ แล้ว<br>แต่ยังไม่เคยได้เขียนรีวิวเลยสักคลาส นี่เป็นครั้งแรกที่เขียนถึง<br>เพราะมันดีจริงๆ จนอดที่จะบอกต่อไม่ได้ฮะ</p><p>หวัดดีปีใหม่ทุกคนเลยนะครับ<br>ขอให้มีความสุขตลอดปี 🙇🏻‍♂️</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5775f3772000" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Fix Subtle Working File Color in IntelliJ IDEA | Quick Settings Guide]]></title>
            <link>https://fresult.medium.com/how-to-fix-subtle-working-file-color-in-intellij-idea-quick-settings-guide-65e50006fb9c?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/65e50006fb9c</guid>
            <category><![CDATA[ide]]></category>
            <category><![CDATA[intellij]]></category>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[jetbrains]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Sun, 26 Oct 2025 05:50:17 GMT</pubDate>
            <atom:updated>2025-10-26T15:33:42.876Z</atom:updated>
            <content:encoded><![CDATA[<h3>Working File Background Color in IntelliJ’s File Navigation Too Subtle? Here’s How to Change It!</h3><p>Sometimes I work with IntelliJ themes that make it really hard to see which file (in Navigation panel) I’m currently working on.<br>The background color of the active file is too subtle.<br>It doesn’t have enough contrast with other files in the navigation panel.</p><p>This makes me annoyed because I like to look at files near my current file.<br>But I can’t find them easily.<br>I waste time trying to figure out which file I’m actually working on.</p><p><strong>See the image below — can you spot the working file?</strong></p><figure><img alt="IntelliJ navigation panel with subtle active file background color that’s hard to see" src="https://cdn-images-1.medium.com/max/491/1*WBqFd3SsuQuBRj-REddOww.png" /></figure><p><strong>The problem gets worse on my second monitor.</strong><br>I use a blue light filter and also use low brightness on that screen to protect my eyes.<br>The filter makes the color even more subtle.<br>I literally cannot see any difference between the working file and normal files at all.</p><figure><img alt="IntelliJ editor with blue light filter showing barely visible active file highlight" src="https://cdn-images-1.medium.com/max/1024/1*HB0ZThiHUqt8eh_JDpUQ-g.png" /><figcaption>The navigation panel on my second monitor screen, which is captured by my phone</figcaption></figure><h4>What Are the Solutions?</h4><p><strong>I found three ways to fix this:</strong><br><strong>Solution 1:</strong> Just use another theme!</p><p><strong>Solution 2:</strong> Be a good citizen — open a PR to fix the theme<br>We could discuss this with our favorite theme maintainer.<br>Then submit a Pull Request to improve the default colors.<br>This helps everyone using the theme!</p><p><strong>Solution 3:</strong> Quick fix — change the color ourselves in settings<br>I love my <a href="https://github.com/luisfer0793/theme-carbon"><strong>Carbon theme</strong></a> too much to switch to anything else.<br>And I need this fixed today, not after a PR review process (which might not even get approved).</p><p><strong>So I went with solution 3.<br>Problem solved in 30 seconds.</strong></p><h4>How to Change the Working File Background Color</h4><p><strong>Here’s the step-by-step process:</strong></p><figure><img alt="IntelliJ Settings: “Appearance &amp; Behavior” &gt; “File Colors” &gt; “Add” (+)" src="https://cdn-images-1.medium.com/max/967/1*X8Q7HOwiZnZveAeKggioIQ.png" /></figure><figure><img alt="IntelliJ “File Colors” settings: “Open Files” scope with “Rose” color selected" src="https://cdn-images-1.medium.com/max/969/1*6BdpiuzlMwFS192l8R8jow.png" /></figure><ol><li>Open “Settings” (press CMD+, on Mac or CTRL+, on Windows)</li><li>Click on “Appearance &amp; Behavior” in the left sidebar</li><li>Click on “File Colors”</li><li>Find and select “Open Files” from the scopes list</li><li>Choose any color you prefer (I picked “Rose” for now - it must be bright enough)</li></ol><h4>The Result</h4><p><strong>Let’s see how it looks now:</strong></p><figure><img alt="IntelliJ navigation showing open files in rose color with HedgingConfiguration.kt selected" src="https://cdn-images-1.medium.com/max/491/1*bwp9okdVMmrwAEm3uGkKpA.png" /></figure><p>Perfect! Now all my open files have a rose background color.<br>My current working file is HedgingConfiguration.kt.<br>We can see it has both the rose color AND the selected file highlight on top of it.</p><blockquote><strong>Is it pretty? No.<br>Does it work? Absolutely! 😂</strong></blockquote><p>The good news is I can always customize the “Open Files” background color again later if I want something less ugly.<br>But for now, this setup saves my life.<br>I can finally see which file I’m working on without wasting time.</p><p>If we face the same problem, give this trick a try!<br>It takes just 30 seconds to set up.</p><h3>Bonus Tip: Auto-Select Our Working File</h3><p>Click the t<strong>hree-dot menu</strong> at the top of the navigation panel.<br>Enable “Always Select Opened File”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/929/1*AXkV0t-NiMbfQB-q9wOdlg.png" /><figcaption>IntelliJ navigation panel menu with “Always Select Opened” File option enabled</figcaption></figure><p>Now IntelliJ automatically scrolls to your current file and highlights it in the tree.<br>No more manual scrolling to find where you are.<br>It’s a perfect combo with your new background color!</p><p><strong>Thanks for reading until the final line.<br>If you’re confused or have any questions, don’t hesitate to leave the comments.</strong></p><p>Feel free to connect with me and stay updated on the latest insights and discussions.<br>👉 <a href="https://linkedin.com/in/fResult">https://linkedin.com/in/fResult</a> 👈</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=65e50006fb9c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Personal Notes: Documenting PR Changes with AI (no Agent mode version)]]></title>
            <link>https://fresult.medium.com/personal-notes-documenting-pr-changes-with-ai-no-agent-mode-version-1be5c36ecee1?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/1be5c36ecee1</guid>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[generative-ai-use-cases]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <category><![CDATA[version-control]]></category>
            <category><![CDATA[git]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Sun, 21 Sep 2025 11:01:36 GMT</pubDate>
            <atom:updated>2025-09-21T15:24:11.972Z</atom:updated>
            <content:encoded><![CDATA[<h3>3 Steps Documenting Pull Request Changes with AI (no Agent mode version)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*z_xZDqFNvob8g34D" /><figcaption>Photo by <a href="https://unsplash.com/@beatriz_perez?utm_source=medium&amp;utm_medium=referral">Beatriz Pérez Moya</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I’m working on a small practice project based on Josh Long’s <strong>Reactive Spring</strong> book.</p><p><a href="https://leanpub.com/reactive-spring">Reactive Spring</a></p><p>I made many little changes, and it’s hard to track them all by hand.<br>So I use an AI to draft my PR description for later review/revisit.</p><blockquote>We can use this on any PR we open for code review</blockquote><p><strong>Here are the steps I took:</strong></p><h3>1. Capture the diff</h3><p><strong>I run:</strong></p><pre>git diff source/main...HEAD &gt; changes.diff<br><br># Generic version<br>git diff [«remote_name»/]«base_branch»...«checked_out_commit» &gt; «target_file»<br></pre><ul><li>source is my remote name (yours might be origin or something else)</li><li>main is the branch we compare against (Don’t make changes to main directly on your real work 😂)</li><li>changes.diff holds every code change since that branch</li></ul><p><strong>You can also run:</strong></p><pre>git diff origin/development...HEAD &gt; changes.diff<br># OR<br>git diff origin/feature/xyz...HEAD &gt; changes.diff</pre><p>Here’s what our git remote -v looks like in this repo:</p><pre>➜ git remote -v<br>origin  git@gitlab.com:fResult/learn-spring-webflux-3.0.git (fetch)<br>origin  git@gitlab.com:fResult/learn-spring-webflux-3.0.git (push)<br>source  git@github.com:fResult/Learn-Spring-Webflux-3.0.git (fetch)<br>source  git@github.com:fResult/Learn-Spring-Webflux-3.0.git (push)</pre><h4>Our diff looks like this:</h4><p><a href="https://gist.github.com/fResult/bf98b1fc574c7bf452d2a8c48f6b95ba#file-changes-diff-L88-L127">https://gist.github.com/fResult/bf98b1fc574c7bf452d2a8c48f6b95ba#file-changes-diff-L88-L127</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1o5A-eBTau8UUTknZgZqNw.png" /></figure><h3>2. Paste into an AI tool with a prompt</h3><p>We open our favorite AI (ChatGPT, Claude, Gemini…) and paste the diff:</p><pre>[[Paste the diffs from your `changes.diff` file here]]<br><br>Hey, bro!<br>From above, I just opened a new GitHub Pull Request with these changes.<br>Could you help me summarize what’s been modified in this PR?<br><br>[[Add your background and/or special commands]]</pre><p>The special commands I added are…</p><pre>.<br>.<br>A bit of background:<br>- This is part of a practice project I built while following &quot;Reactive Spring&quot;, authored by Josh Long.<br>- I was curious about retry logic and built a small Kotlin DSL for exponential backoff.<br><br>Please remember,<br>this is just for personal reference,<br>not a release note, so don&#39;t make it too formal!</pre><blockquote><strong><em>Note:</em></strong><br>We can say:<br>“This is just for the <strong>code reviewer</strong>, not a release note, don’t make it too formal!”</blockquote><h3>3. Review the AI’s summary</h3><blockquote>Please check carefully, it can make things wrong.</blockquote><p>The AI might return something like this:</p><pre># Lesson 08: Raw RSocket sub-project (Bidirectional, Metadata)<br><br>## What’s in this batch of changes?<br><br>- **README tweaks**  <br>    - Added notes on Gradle Kotlin-DSL quirks (type-safe vs. string deps, afterEvaluate, plugin `apply(false)`)<br>- **Build updates**  <br>    - Pulled in `jackson-module-kotlin` so our data classes actually deserialize  <br>    - Added Netty’s macOS ARM DNS resolver to silence the native-lib warnings<br>- **EncodingUtils cleanup**  <br>    - Switched metadata reader to a proper `TypeReference&lt;Map&lt;String,Any&gt;&gt;`  <br>    - Hoisted duplicate exception‐handling into one helper  <br>    - Removed the extra `ObjectMapper` bean—only `EncodingUtils` now<br>- **Retry DSL**  <br>    - New tiny DSL under `dsl.retry`: `RetryConfig`, `RetryConfigBuilder`, and a `retryBackoffOnClosedChannel { … }` factory  <br>    - Client now does `.retryWhen(retryBackoffOnClosedChannel { maxAttempts = 5 })` with backoff + jitter + ClosedChannel filter<br>- **Bidirectional RSocket example**  <br>    - New raw-rsocket modules for server &amp; client (DTOs, launcher, service)  <br>    - Client launcher refactored to named handlers (`onGreetingReceived`, `onGreetingError`, `onGreetingComplete`)  <br>    - Service now logs “Finished greeting to &lt;clientId&gt;.” so you know which client ended<br>- **Misc**  <br>    - Added Gradle `bootBidirectionalService` &amp; `bootBidirectionalClient` tasks  <br>    - Cleaned up some unused imports in Fire-and-Forget example<br><br>Basically just personal clean-ups and a small retry DSL on top of the book’s starting code.</pre><p>Read it! Update any details, and drop it into our PR.</p><p>Now, anyone who reviews our code, or even we ourselves, month after month, can quickly see what we did and why.</p><blockquote><strong><em>Tip:<br></em></strong>Always glance over the AI’s output.<br>Make sure it reflects exactly what we did and our thoughts.</blockquote><h4>See my Pull Request:</h4><p><a href="https://github.com/fResult/Learn-Spring-Webflux-3.0/pull/20">Lesson 08: Raw RSocket sub-project (Bidirectional, Metadata) by fResult · Pull Request #20 · fResult/Learn-Spring-Webflux-3.0</a></p><p>Happy coding, PR writing, and documenting!</p><h3>Appendix</h3><p>To see all changes since branching off the base branch:</p><pre>git diff [«remote_name»/]«base_branch»…HEAD &gt; changes-since-base.diff</pre><p>To see the diff of <em>staged</em> files (added with git add) against HEAD:</p><pre>git diff --staged &gt; changes-staged.diff</pre><p>To see the diff of working tree changes (not yet <strong>staged</strong>) against HEAD:</p><pre>git diff HEAD &gt; changes-working.diff</pre><p><strong>In case you use OSX</strong>, you can copy diffs to the clipboard directly:</p><pre>git diff --staged | pbcopy</pre><p><strong>Thanks for reading until the final line.<br>If you’re confused or have any questions, don’t hesitate to leave the comments.</strong></p><p>Feel free to connect with me and stay updated on the latest insights and discussions.<br>👉 <a href="https://linkedin.com/in/fResult">https://linkedin.com/in/fResult</a> 👈</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1be5c36ecee1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Function Signatures: The Power of Low Entropy Design]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://fresult.medium.com/function-signatures-the-power-of-low-entropy-design-31014f433b49?source=rss-495fbfb852f0------2"><img src="https://cdn-images-1.medium.com/max/2600/1*PAtqt2XjGr5Iow8giUTXdA.png" width="3297"></a></p><p class="medium-feed-snippet">Why well-designed method signatures lead to clearer, safer, and more maintainable code</p><p class="medium-feed-link"><a href="https://fresult.medium.com/function-signatures-the-power-of-low-entropy-design-31014f433b49?source=rss-495fbfb852f0------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://fresult.medium.com/function-signatures-the-power-of-low-entropy-design-31014f433b49?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/31014f433b49</guid>
            <category><![CDATA[entropy]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Tue, 03 Jun 2025 20:23:16 GMT</pubDate>
            <atom:updated>2026-08-29T16:44:21.431Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Elegant Partial Updates in REST APIs: A Guide for Java and Kotlin]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://fresult.medium.com/elegant-partial-updates-in-rest-apis-a-guide-for-java-and-kotlin-04e9c014bc3e?source=rss-495fbfb852f0------2"><img src="https://cdn-images-1.medium.com/max/996/1*Fv23r6cTuzNyn60F2bfXDQ.png" width="996"></a></p><p class="medium-feed-snippet">Explore how to make clean partial updates in REST APIs using the Functional Approach and prefer pre-factor rather than refactor&#x2026;</p><p class="medium-feed-link"><a href="https://fresult.medium.com/elegant-partial-updates-in-rest-apis-a-guide-for-java-and-kotlin-04e9c014bc3e?source=rss-495fbfb852f0------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://fresult.medium.com/elegant-partial-updates-in-rest-apis-a-guide-for-java-and-kotlin-04e9c014bc3e?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/04e9c014bc3e</guid>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[functional-programming]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Fri, 23 May 2025 20:26:15 GMT</pubDate>
            <atom:updated>2026-10-12T17:16:31.321Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[The Power of @Nonnull and @Nullable In Jakarta EE (Java)]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://fresult.medium.com/the-power-of-nonnull-and-nullable-in-jakarta-ee-java-5fb3875929dc?source=rss-495fbfb852f0------2"><img src="https://cdn-images-1.medium.com/max/2600/0*DSQMWr1v2gvS4lIq" width="6016"></a></p><p class="medium-feed-snippet">Tired of NullPointerException? Let&#x2019;s improve our API (method) from &#x201C;ambiguous&#x201D; to &#x201C;explicit&#x201D; by using @Nonnull, @Nullable, and Optional!</p><p class="medium-feed-link"><a href="https://fresult.medium.com/the-power-of-nonnull-and-nullable-in-jakarta-ee-java-5fb3875929dc?source=rss-495fbfb852f0------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://fresult.medium.com/the-power-of-nonnull-and-nullable-in-jakarta-ee-java-5fb3875929dc?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/5fb3875929dc</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[function-signature]]></category>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[null-safety]]></category>
            <category><![CDATA[functional-programming]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Sun, 18 May 2025 13:27:13 GMT</pubDate>
            <atom:updated>2026-08-27T16:18:51.301Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[บันทึกเรื่องการปิ๊งแว่บไอเดีย Union Type Parameter ใน Java ของข้าพเจ้าเอง]]></title>
            <link>https://fresult.medium.com/%E0%B8%9A%E0%B8%B1%E0%B8%99%E0%B8%97%E0%B8%B6%E0%B8%81%E0%B9%80%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%9B%E0%B8%B4%E0%B9%8A%E0%B8%87%E0%B9%81%E0%B8%A7%E0%B9%88%E0%B8%9A%E0%B9%84%E0%B8%AD%E0%B9%80%E0%B8%94%E0%B8%B5%E0%B8%A2-union-type-parameter-%E0%B9%83%E0%B8%99-java-%E0%B8%82%E0%B8%AD%E0%B8%87%E0%B8%82%E0%B9%89%E0%B8%B2%E0%B8%9E%E0%B9%80%E0%B8%88%E0%B9%89%E0%B8%B2%E0%B9%80%E0%B8%AD%E0%B8%87-89f5eaea9b55?source=rss-495fbfb852f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/89f5eaea9b55</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[typelevel-programming]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[union-types]]></category>
            <dc:creator><![CDATA[fResult]]></dc:creator>
            <pubDate>Sun, 11 May 2025 13:44:03 GMT</pubDate>
            <atom:updated>2025-05-18T12:29:41.500Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OzGiFLk_f_efoHdL" /><figcaption>Photo by <a href="https://unsplash.com/@acton_crawford?utm_source=medium&amp;utm_medium=referral">Acton Crawford</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Table of Contents</h3><ul><li><a href="#1bd1">ยาวไป…ไม่อ่าน</a></li><li><a href="#acf1">Main Content</a></li><li><a href="#bb55">Conclusion &amp; Final Thought</a></li><li><a href="#7459">Appendix</a> (ตรงนี้ Code ยาวจัด… ไม่ต้องอ่านก็ได้ครับ)</li></ul><h3>ยาวไปไม่อยากอ่านอะ (มาอ่านสั้นๆ ตรงนี้ได้เลยฮะ)</h3><p>ถึงแม้ Java ไม่มี Union Type แบบภาษาอื่น<br>แต่เราสามารถจำลองมันได้ด้วย <strong>sealed interface</strong> ร่วมกับ <strong>record</strong> หรือ <strong>class</strong> ได้นะ<br>ซึ่งจะทำให้ compiler ช่วยตรวจจับ error ได้แต่เนิ่นๆ โดยไม่ต้องรอให้ code พังตอน runtime (ได้รับ fast feedback loop) และช่วยลดการเขียน Method Overloading ในภาษา Java ได้ฮะ</p><p><strong>ตัวอย่าง:</strong></p><pre>// Mimick TypeScript&#39;s Union Type like declaring `type Result = Success | Failure`<br>sealed interface Result&lt;S, E&gt; permits Success, Failure {}<br>record Success&lt;S, E&gt;(S value) implements Result&lt;S, E&gt; {}<br>record Failure&lt;S, E&gt;(E error) implements Result&lt;S, E&gt; {}<br><br>record User(String name) {}<br><br>public String handle(Result&lt;User, NotFoundException&gt; result) {<br>  return switch (result) {<br>    case Success&lt;User, NotFoundException&gt; s -&gt; &quot;Found user: &quot; + s.value().name();<br>    case Failure&lt;User, NotFoundException&gt; f -&gt; &quot;[ERROR]: &quot; + f.error().message();<br>  };<br>}<br><br>handle(new Success(new User(/*name:*/ &quot;John&quot;)); // Found user: John<br>handle(new Failure(new NotFoundException(&quot;User with name [John] not found!&quot;)); // [ERROR]: User with name [John] not found!</pre><blockquote>ตัวอย่างจากเนื้อหาหลักจะ Simple กว่านี้ (มั้ง) นะ <em>😅</em></blockquote><h3>Main Content</h3><p>ในบทความนี้เป็นบันทึกแบบสั้นๆ ที่ผมบังเอิญนึกขึ้นได้ในระหว่างกำลังอ่านๆ เกี่ยวกับ <a href="https://rockthejvm.com/articles/type-level-programming-in-scala-part-1-numbers-and-comparisons#numbers-as-types">Type-Level Programming in Scala</a> อยู่เพลินๆ ก็แว่บไอเดียว่านี่มันมีความคล้ายกับ Union Type ในภาษา TypeScript ที่คุ้นเคยอยู่เหมือนกันนะเนี่ย</p><p><a href="https://rockthejvm.com/articles/type-level-programming-in-scala-part-1-numbers-and-comparisons">Type-Level Programming in Scala: Part 1 - Numbers and Comparisons</a></p><p>และเนื่องจากเคยมีคำถามที่ติดใจกับตัวเองมาตลอดว่า ทำไม Java/Kotlin ถึงไม่ทำ Union Type แบบ TypeScript บ้างน้าาา?</p><p>และเรื่องที่อ่านเจอนี้ก็มีความใกล้เคียงกับ TypeScript ซะด้วย งานนี้คงไม่ลองไม่ได้แล้วล่ะฮะะ</p><blockquote><strong><em>เมื่อก่อนผมดีลกับ Kotlin/Java ได้ยังไงในเคสที่ต้องการทำงาน Union Type?<br></em></strong>คำตอบก็คือผมใช้ Method Overloading แทนฮะ</blockquote><p>คิดได้ดังนั้นก็เปิด IntelliJ สิครับ (ไม่ขอรอประธานที่ไหนมาตัดริบบิ้นให้ทั้งนั้น 555)</p><p>เริ่มจากประกาศ Permissionให้ Read และ Write sealed record classes เสียก่อน<br>(ที่จริงแล้วไม่ต้อง sealed record classes แต่อาจเป็นแค่ interface และ classes ก็ได้นะ)</p><pre>sealed interface Permission permits Read, Write {}<br>record Read() implements Permission {}<br>record Write() implements Permission {}<br>record Execute() {};</pre><blockquote><strong><em>Note:<br></em></strong>จากข้างบน ผมจงใจไม่ใส่ implements Permission interface ให้กับ Execute record class เพื่อเอาไว้ทดสอบเรื่อง type-safety ในภายหลังนะฮะ</blockquote><p>Read, Write record class ซึ่ง implement จาก Permission interface ก็จะเลียนแบบการทำงานของ Union Type ใน TypeScript ประมาณนี้ล่ะมั้งนะ</p><pre>type Permission = Read | Write<br><br>type Read = {}<br>type Write = {}</pre><p>จากนั้นก็ประกาศ method ที่ต้องการให้รับ parameter ได้ทั้ง Read และ Write แบบนี้</p><pre>class ResourceAction {<br>  public String perform(Permission permission) {<br>    return String.format(&quot;Performing action with %s...&quot;, permission.getClass().getSimpleName());<br>  }<br>}</pre><p>และเราจะปิดท้ายด้วย main method</p><pre>class TestSecureResource {<br>  public static void main(String[] args) {<br>    final var resourceAction = new ResourceAction();<br>    System.out.println(resourceAction.perform(new Read()));    // Performing action with Read...<br>    System.out.println(resourceAction.perform(new Write()));   // Performing action with Write...<br>    System.out.println(resourceAction.perform(new Execute())); /* Compilation Error!!!!<br>                                                                * Required Type: Permission<br>                                                                * Provided: Execute<br>                                                                */<br>  }<br>}</pre><p>และเราจะเห็นได้ว่า บรรทัดที่เรา assign instance ของ Execute record class ให้เป็น argument ของ ResourceAction::perform method นั้น<strong>จะติด Compilation Error</strong> ในทันที เนื่องจากไม่ผ่าน type constraint validation นั่นเองฮะ</p><p>ต่อไปนี้เป็นตัวอย่างที่นำไปใช้ทดลอง run ได้เลยครับ</p><p>ใน<strong>ไฟล์แรก</strong> เป็น code ตามในบทความนี้<br>ส่วนใน<strong>ไฟล์ที่ 2</strong> เป็น code ที่ผมลอง POC เล่นไปเรื่อยเปื่อย 😂</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/70ec16d59b071f0ab6ab7aeea5b01a9a/href">https://medium.com/media/70ec16d59b071f0ab6ab7aeea5b01a9a/href</a></iframe><h3>Conclusion</h3><p>การนำแนวคิดนี้ไปใช้ช่วยให้ Java สามารถจัดการชนิดข้อมูล (Type Constraints) ในลักษณะที่คล้ายกับ Union Type ใน TypeScript ได้<br>ถึงแม้ว่า Java จะไม่มี feature นี้มาให้โดยตรงก็ตามด<br>แต่ด้วยการใช้ Sealed Interface และ Generic Type Constraints (อย่างที่เห็นใน Permission interface)<br>จะช่วยให้เราลดการเขียน Method Overloading (เช่น perform(Read), และ perform(Write) แยกกัน) ซึ่งทำให้ code เราสะอาดขึ้น และยังคงได้ประโยชน์จาก Type Safety ฮะ</p><h3>Final Thought</h3><p>และแน่นอนว่า เมื่อเราจัดการ Logic บางอย่างในระดับ Type-level<br>ก็ยังช่วยให้เราสร้างระบบที่มี Type System ที่แข็งแกร่งและปลอดภัยมากขึ้นได้<br>เพราะเราจะได้ตรวจสอบความถูกต้องได้ตั้งแต่ Compile-Time และไม่ต้องไปลุ้นเอาตอน Runtime ให้ปวดหัวนั่นเองฮะ (หมายความว่าเราจะได้รับ fast feedback ซึ่ง IDE จะแจ้งเตือนให้เราแก้ไขทันที)<br>แบบนี้มันก็จะช่วยลด bug และเพิ่มความมั่นใจในการเขียน code ไปได้เยอะเลยนะ ว่ามะ?</p><p>สำหรับวันนี้ขอตัวไปก่อนล่ะฮะ</p><p>สวัสดีครับ 🙇‍♂️</p><p><strong>Thanks for reading until the final line.<br>If you’re confused or have any questions, don’t hesitate to leave the comments.</strong></p><p>Feel free to connect with me and stay updated on the latest insights and discussions.<br>👉 <a href="https://linkedin.com/in/fResult">https://linkedin.com/in/fResult</a> 👈</p><p>And happy coding 🫰🫰</p><h3>Appendix</h3><p>ส่วนนี้จะเป็น Code ที่ผมได้ลอง Type-Level Programming แบบเต็มข้อในภาษา Java ดู ท่านใดที่สนใจก็ตามมาตำกันได้เลยฮะ (ใครใคร่ลอง… copy ไปลองได้เลย)</p><p><a href="https://gist.github.com/fResult/77c4341a49f8135ea8762b84b3d7fffe">https://gist.github.com/fResult/77c4341a49f8135ea8762b84b3d7fffe</a></p><p>แต่ไม่ต้องดูก็ได้นะ…. เพราะว่า code ยาวจัดๆ เลยครับ 🤣</p><p><strong>อันนี้เป็น Code as a Glimpse (เอามาใส่ไว้ให้ดูแบบผ่านๆ ได้):</strong></p><pre>sealed interface Task permits Todo, Doing, Done;<br>record Todo(Long id, String title, String description) implements Task;<br>record Doing(Long id, String title, String description) implements Task;<br>record Done(Long id, String title, String description, Instant completedAt) implements Task;<br><br>class TaskUseCase {<br><br>  public TaskResponse advanceTask(Long id) {<br>    return executeTaskOperation(id, taskManagement::advanceTask);<br>  }<br><br>  public TaskResponse revertTask(Long id) {<br>    return executeTaskOperation(id, taskManagement::revertTask);<br>  }<br><br>  public TaskResponse markAsDone(Long id) {<br>    return executeTaskOperation(id, taskManagement::markAsDone);<br>  }<br><br>  public TaskResponse resetToTodo(Long id) {<br>    return executeTaskOperation(id, taskManagement::resetToTodo);<br>  }<br><br>  private TaskResponse executeTaskOperation(Long id, Function&lt;Task, Task&gt; operation) {<br>    return taskRepository.findById(id)<br>        .map(operation.andThen(taskRepository::save).andThen(TaskResponse::fromDomain))<br>        .orElseThrow(throwNotFound(id, Task.class));<br>  }<br>}<br><br>class TaskManagement {<br>  public Task advanceTask(Task task) {<br>    return switch (task) {<br>      case Todo(Long id, String title, String description) -&gt; new Doing(id, title, description);<br>      case Doing(Long id, String title, String description) -&gt; new Done(id, title, description);<br>      case Done ignored -&gt; task;<br>    };<br>  }<br><br>  public Task revertTask(Task task) {<br>    return switch (task) {<br>      case Done(Long id, String title, String description, Instant ignored) -&gt; new Doing(id, title, description);<br>      case Doing(Long id, String title, String description) -&gt; new Todo(id, title, description);<br>      case Todo ignored -&gt; task;<br>    };<br>  }<br><br>  public Task markAsDone(Task task) {<br>    if (task instanceof Done) return task;<br><br>    return new Done(task.id(), task.title(), task.description());<br>  }<br><br>  public Task resetToTodo(Task task) {<br>    if (task instanceof Todo) return task;<br><br>    return new Todo(task.id(), task.title(), task.description());<br>  }<br>}</pre><p><strong>แต่จากบรรทัดนี้เป็นต้นไป…จะไม่มีการ glimpse แล้วนะ 😂</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/008e0b05daaa1799e7f70019f56e39e3/href">https://medium.com/media/008e0b05daaa1799e7f70019f56e39e3/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=89f5eaea9b55" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>