<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged improvement</title>
<link>https://coderlegion.com/tag/improvement</link>
<description>Powered by Question2Answer</description>
<item>
<title>Why Is 'Thinking' Being Attached to Everything?</title>
<link>https://coderlegion.com/27513/why-is-thinking-being-attached-to-everything</link>
<description>&lt;p&gt; I Noticed That Every Profession Now Has a &quot;Thinking.&quot; I Went Looking for Why.&lt;/p&gt;
&lt;p&gt;It started as a low-grade irritation.&lt;/p&gt;
&lt;p&gt;Systems thinking on a job listing. &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Design_thinking&quot;&gt;Design thinking&lt;/a&gt; in a workshop invitation. Product thinking in a strategy meeting. Computational thinking on a university course banner. These terms kept appearing, and each time they did, I felt a small, specific unease. The kind you get when you suspect everyone else in the room understands something you do not.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Eventually I stopped treating that unease as a sign of my own ignorance and started treating it as a question worth answering. Why is &quot;thinking&quot; being attached to everything? What does it actually mean when you pair it with another word? And what do all these terms have in common, underneath the branding?&lt;/p&gt;
&lt;p&gt;I went looking for answers. What I found surprised me.&lt;/p&gt;
&lt;p&gt;Before the pairing makes sense, the word &quot;thinking&quot; itself needs to. And it turns out thinking is not one thing. It is a whole family of mental activities, from recalling a memory to constructing an argument to making a split-second call. The most useful insight I came across was a distinction that psychologist Daniel Kahneman made famous: our brains operate in two distinct modes.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;System 1 is fast, automatic, and effortless. It reads the emotion on a face before you consciously decide to look. It finishes the sentence before you hear the end of it. For most of our waking hours, this is what is running the show.&lt;/p&gt;
&lt;p&gt;System 2 is slow, deliberate, and genuinely hard. It is what you use to follow a complex argument, to notice that your first reaction might be wrong, to work through something you have never encountered before. The brain avoids it when it can,  not out of laziness, exactly, but out of efficiency. System 2 is metabolically expensive. The brain would rather not.&lt;/p&gt;
&lt;p&gt;I filed that away, not yet sure what to do with it. It came back to me later, in a different form.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;When you place a modifier in front of &quot;thinking,&quot; something interesting happens on both sides of the pairing.&lt;/p&gt;
&lt;p&gt;The modifier shapes the thinking. Left alone, thinking is unfocused. It wanders, it confirms what you already believe, it reaches for the nearest comfortable conclusion. Adding a modifier gives it a track to run on. The word &quot;critical&quot; in critical thinking does not simply describe a topic; it issues an instruction to the brain. Look for flaws. Question assumptions. Do not accept the first answer that arrives. Without that word, you might still be thinking, but your brain is probably just agreeing with itself.&lt;/p&gt;
&lt;p&gt;And thinking reshapes the modifier. This is the less obvious half of the relationship. Take the word &quot;design.&quot; On its own, it is a noun — an artifact, a layout, an outcome. Add &quot;thinking&quot; and the entire emphasis shifts. You are no longer focused on the thing being made. You are focused on the cognitive process of making it: the empathy, the iteration, the willingness to be wrong and start again. A noun becomes a methodology.&lt;/p&gt;
&lt;p&gt;This is the pattern underneath all of these terms. When you see &quot;X thinking,&quot; you are not being pointed toward a subject. You are being handed a cognitive lens.  A structured way of seeing and moving through a particular class of problem.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Three examples make this concrete, and they work best when set against each other.&lt;/p&gt;
&lt;p&gt;Critical thinking instructs the brain to be suspicious. If a headline claims that coffee drinkers live longer, the instinct is to believe or dismiss it based on how it feels. Critical thinking intercepts that instinct and routes you somewhere harder: Who funded the study? How large was the sample? Is correlation being reported as causation? What do other studies say? The goal is not skepticism for its own sake. It is the suspension of judgment until you have actually earned a position.&lt;/p&gt;
&lt;p&gt;Design thinking starts somewhere different,  with a person rather than a problem. A bank that wants to improve its mobile app might survey customers and build what they ask for. A team practicing design thinking observes customers using the app in real conditions, notices that what users are actually experiencing is anxiety, not knowing whether a transaction went through — and prototypes a small confirmation animation before writing a single line of production code. The solution that emerges is not the one anyone would have asked for on a survey. That is the point.&lt;/p&gt;
&lt;p&gt;Systems thinking instructs you to look at how parts affect each other rather than the parts in isolation. A hospital hires more doctors to reduce wait times. Wait times increase. More doctors means more patients seen, which means more follow-up appointments, which means the scheduling system clogs further downstream. A systems thinker would have mapped that feedback loop before the hiring decision. The intervention was obvious; the consequences were not, until you looked at the whole.&lt;/p&gt;
&lt;p&gt;These three frameworks feel different from each other  and they are. But they share something underneath. Each one is a deliberate System 2 override. Each was built to intercept the comfortable, energy-efficient shortcuts that System 1 wants to take and route the brain into the slow, uncomfortable processing the problem actually requires. All of these frameworks, at their core, are structured workarounds for the brain's preference for the easy answer.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Why is this pattern everywhere right now? Three things, I think.&lt;/p&gt;
&lt;p&gt;The most durable reason is that specific knowledge goes stale but mental models do not. Teaching someone the steps for a particular tool has a shelf life. Teaching them design thinking , a repeatable approach to any human-centered problem like travels across roles, industries, and decades. Organizations are increasingly reaching for that durability.&lt;/p&gt;
&lt;p&gt;The second reason is that these terms dissolve gatekeeping. &quot;Design&quot; used to belong to designers. &quot;Data&quot; used to belong to analysts. Re-framing them as types of thinking signals that anyone  (engineers, marketers, executives) can and should adopt that perspective. Whether that is always earned in practice is a separate debate. The intent is democratizing.&lt;/p&gt;
&lt;p&gt;And then there is the honest third reason: this is a good brand. &quot;Design thinking&quot; sounds like a fundamental upgrade to how your mind works. &quot;Design workflow&quot; sounds like a checklist. Packaging methodology as cognition makes it easier to sell as workshops, certifications, consulting engagements. The frameworks are often genuinely valuable. But their proliferation is not purely intellectual. The brand is doing real work, and it would be naive to pretend otherwise.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Now when I encounter a new &quot;X thinking&quot; term I have never heard, I reach for five questions.&lt;/p&gt;
&lt;p&gt;What domain does this framework operate in? What specific failure mode was it invented to fix ? What does it exist to prevent? What does it tell you to pay attention to that your default thinking ignores? What does it tell you to resist or question? And what are the actual practices , not the concept, but the things you would do differently on a Tuesday morning?&lt;/p&gt;
&lt;p&gt;The answers will not come from knowing what &quot;thinking&quot; means. That word tells you a structured method exists. It does not tell you what the method is. For that, you have to go learn the specific framework , its history, its steps, its blind spots. But at least now you know exactly what you are looking for.&lt;/p&gt;
&lt;p&gt;When I see those job listings and workshop banners now, I do not feel the old unease. I feel something closer to recognition. Each term marks a place where someone looked at a class of problem, noticed that human beings approach it badly by default, and spent real effort designing a path through. That is not jargon. That is worth learning.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27513/why-is-thinking-being-attached-to-everything</guid>
<pubDate>Thu, 17 Sep 2026 11:16:18 +0000</pubDate>
</item>
<item>
<title>Improving my previous OpenRewrite recipe</title>
<link>https://coderlegion.com/3367/improving-my-previous-openrewrite-recipe</link>
<description>&lt;p&gt;I started discovering OpenRewrite last week by writing a Kotlin recipe that moves Kotlin files according to the official directory structure recommendation. I mentioned some future works, and here they are. In this post, I want to describe how to compute the root package instead of letting the user set it.&lt;/p&gt;
&lt;h3 id=&quot;reminder&quot;&gt;Reminder&lt;/h3&gt;
&lt;p&gt;I developed last week a recipe to follow the Kotlin recommendation regarding directory structure:&lt;/p&gt;
&lt;p&gt;&amp;gt;In pure Kotlin projects, the recommended directory structure follows the package structure with the common root package omitted. For example, if all the code in the project is in the &lt;code&gt;org.example.kotlin&lt;/code&gt; package and its subpackages, files with the &lt;code&gt;org.example.kotlin&lt;/code&gt; package should be placed directly under the source root, and files in &lt;code&gt;org.example.kotlin.network.socket&lt;/code&gt; should be in the network/socket subdirectory of the source root.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://kotlinlang.org/docs/coding-conventions.html#source-code-organization&quot;&gt;Directory structure&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In a Java project, if you have packages &lt;code&gt;ch.frankel.foo&lt;/code&gt;, &lt;code&gt;ch.frankel.bar&lt;/code&gt;, and &lt;code&gt;ch.frankel.baz&lt;/code&gt;, you'll get the following structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src
 |__ main
    |__ java
       |__ ch
          |__ frankel
             |__ foo
             |__ bar
             |__ baz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Kotlin projects, many developers follow the same structure as above, but it can be flattened as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src
 |__ main
    |__ kotlin
       |__ foo
       |__ bar
       |__ baz
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-work&quot;&gt;The work&lt;/h3&gt;
&lt;p&gt;My recipe's original version mandated that you configure the root package yourself, &lt;em&gt;e.g.&lt;/em&gt;, &lt;code&gt;ch.frankel&lt;/code&gt; for the above example. However, it should be possible to compute it automatically, from looking at the source files. It adds an extra step to the process: before moving the file to the root, the recipe should look at each source file, get the package, compute the longest prefix with the existing root, make it the root, and go to the next source file. The regular &lt;code&gt;Recipe&lt;/code&gt; doesn't work in this case. We need to switch to a &lt;code&gt;ScanningRecipe&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&amp;gt;If a recipe needs to generate new source files or needs to see all source files before making changes, it must be a &lt;code&gt;ScanningRecipe&lt;/code&gt;. A &lt;code&gt;ScanningRecipe&lt;/code&gt; extends the normal &lt;code&gt;Recipe&lt;/code&gt; and adds two key objects: an &lt;code&gt;accumulator&lt;/code&gt; and a &lt;code&gt;scanner&lt;/code&gt;. The &lt;code&gt;accumulator&lt;/code&gt; object is a custom data structure defined by the recipe itself to store any information the recipe needs to function. The &lt;code&gt;scanner&lt;/code&gt; object is a visitor which populates the &lt;code&gt;accumulator&lt;/code&gt; with data.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.openrewrite.org/concepts-and-explanations/recipes#scanning-recipes&quot;&gt;Scanning recipes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Scanning recipes offer two steps: the first to gather data, the second to do the work.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14758325757968137092&quot; alt=&quot;ScanningRecipe class diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;We must design our algorithm within the constraints of OpenRewrite, and they are the following: in the first phase, for each source file, OpenRewrite will call the &lt;code&gt;getScanner()&lt;/code&gt; method that returns a visitor of our choice. In turn, OpenRewrite calls the visitor's methods, which can access the accumulator.&lt;/p&gt;
&lt;p&gt;My first naive approach was to use a collection as the accumulator, but it's not necessary. The algorithm is much simpler if we set a mutable placeholder that holds the package root and update it if necessary during each visit. The initial value should be &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the value is &lt;code&gt;null&lt;/code&gt;, which happens on the first visitor, set the package root to the source file's package.&lt;/li&gt;
&lt;li&gt;If the value is an empty string, skip–see below.&lt;/li&gt;
&lt;li&gt;In any other case, compute the new package root by finding the longest prefix between the existing package root and the source file's package.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It might result in an empty string, indicating that packages have no common root, &lt;em&gt;e.g.&lt;/em&gt;, &lt;code&gt;ch.frankel.foo&lt;/code&gt; and &lt;code&gt;org.frankel.foo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here's the updated code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class FlattenStructure(private val rootPackage: String?) : ScanningRecipe&amp;lt;AtomicReference&amp;lt;String?&amp;gt;&amp;gt;() { //1-2

  constructor() : this(null)                                                   //3

  override fun getDisplayName(): String = &quot;Flatten Kotlin package directory structure&quot;
  override fun getDescription(): String =
    &quot;Move Kotlin files to match idiomatic layout by omitting the root package according to the official recommendation.&quot;

  override fun getInitialValue(ctx: ExecutionContext) = AtomicReference&amp;lt;String?&amp;gt;(null) //4

  override fun getScanner(acc: AtomicReference&amp;lt;String?&amp;gt;): TreeVisitor&amp;lt;*, ExecutionContext&amp;gt; {
    if (rootPackage != null) return TreeVisitor.noop&amp;lt;Tree, ExecutionContext&amp;gt;() //5
    return object : KotlinIsoVisitor&amp;lt;ExecutionContext&amp;gt;() {
      override fun visitCompilationUnit(cu: K.CompilationUnit, ctx: ExecutionContext): K.CompilationUnit {
        val packageName = cu.packageDeclaration?.packageName ?: return cu
        val computedPackage = acc.get()
        when (computedPackage) {
          null -&amp;gt; acc.set(packageName)                                         //6
          &quot;&quot; -&amp;gt; {}                                                             //7
          else -&amp;gt; {
            val commonPrefix = packageName.commonPrefixWith(computedPackage).removeSuffix(&quot;.&quot;) //8
            acc.set(commonPrefix)
          }
        }
        return cu
      }
    }
  }

  override fun getVisitor(acc: AtomicReference&amp;lt;String?&amp;gt;): TreeVisitor&amp;lt;*, ExecutionContext&amp;gt; {
    return object : KotlinIsoVisitor&amp;lt;ExecutionContext&amp;gt;() {
      override fun visitCompilationUnit(cu: K.CompilationUnit, ctx: ExecutionContext): K.CompilationUnit {
        val packageName = cu.packageDeclaration?.packageName ?: return cu
        val packageToSet: String? = rootPackage ?: acc.get()                   //9
        if (packageToSet == null || packageToSet.isEmpty()) return cu
        val relativePath = packageName.removePrefix(packageToSet).removePrefix(&quot;.&quot;)
          .replace('.', '/')
        val filename = cu.sourcePath.fileName.toString()
        val newPath: Path = Paths.get(&quot;src/main/kotlin&quot;)
          .resolve(relativePath)
          .resolve(filename)
        return cu.withSourcePath(newPath)
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Inherit from &lt;code&gt;ScanningRecipe&lt;/code&gt; instead of directly from &lt;code&gt;Recipe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set the accumulator type to be an &lt;code&gt;AtomicReference&amp;lt;String?&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Make the configuration easier when you don't override the package root&lt;/li&gt;
&lt;li&gt;The initial root is uninitialized&lt;/li&gt;
&lt;li&gt;Skip the computation if the root package is manually set&lt;/li&gt;
&lt;li&gt;If it's the first file visited, the accumulator holds &lt;code&gt;null&lt;/code&gt;, and we can set the (temporary) root as the current package&lt;/li&gt;
&lt;li&gt;One of the previous computations returned no common root–do nothing&lt;/li&gt;
&lt;li&gt;Find the longest common prefix between the held package root and the current package&lt;/li&gt;
&lt;li&gt;The only difference with the original code:&lt;br&gt;
we check if the root package has been set manually otherwise, we use the one computed in the first pass&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;optimizing-the-recipe&quot;&gt;Optimizing the recipe&lt;/h3&gt;
&lt;p&gt;You may have noticed that when there is no common root, &lt;em&gt;e.g.&lt;/em&gt;, &lt;code&gt;ch.frankel.foo&lt;/code&gt; and &lt;code&gt;org.frankel.foo&lt;/code&gt;, we scan all files anyway. In a small codebase, it's not a big issue, but when scanning millions of source files, that's a huge waste of CPU cycles and time. If you run the recipe in the Cloud, it directly translates to money. We should stop scanning as soon as we detect the computed package root is an empty string to optimize the recipe.&lt;/p&gt;
&lt;p&gt;Here's the updated code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;override fun getScanner(acc: AtomicReference&amp;lt;String?&amp;gt;): TreeVisitor&amp;lt;*, ExecutionContext&amp;gt; {
  if (rootPackage != null) return TreeVisitor.noop&amp;lt;Tree, ExecutionContext&amp;gt;()   //1
  val currentPackage = acc.get()
  if (currentPackage == &quot;&quot;) return TreeVisitor.noop&amp;lt;Tree, ExecutionContext&amp;gt;()  //2
  return object : KotlinIsoVisitor&amp;lt;ExecutionContext&amp;gt;() {
    override fun visitCompilationUnit(cu: K.CompilationUnit, ctx: ExecutionContext): K.CompilationUnit {
      val packageName = cu.packageDeclaration?.packageName ?: return cu
      // Different call than the one above!
      val currentPackage = acc.get()
      // First scanned file
      if (currentPackage == null) acc.set(packageName)                         //3
      else {
        // Find the longest common prefix between the stored package and the current one
        val commonPrefix = packageName.commonPrefixWith(currentPackage).removeSuffix(&quot;.&quot;)
        acc.set(commonPrefix)
      }
      return cu
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;If the root package has been set, skip visiting&lt;/li&gt;
&lt;li&gt;If one of the previous computations sets an empty string, there isn't any common root package: skip visiting&lt;/li&gt;
&lt;li&gt;Simplify the visitor by removing the clause when the accumulator is an empty string since it can't happen anymore&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that OpenRewrite still scans each file, but at least doesn't visit it thanks to the no-op visitor.&lt;/p&gt;
&lt;h3 id=&quot;counting-visits&quot;&gt;Counting visits&lt;/h3&gt;
&lt;p&gt;I made a couple of attempts before finding the right approach to the above. To ensure that I got it right, I wanted to display the number of visits by the scanner. We can use the accumulator to increment the visit count. Here are the changes I made:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migrated from an &lt;code&gt;AtomicReference&amp;lt;String?&amp;gt;&lt;/code&gt; to an &lt;code&gt;AtomicReference&amp;lt;Pair&amp;lt;Int, String?&amp;gt;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;getInitialValue()&lt;/code&gt; function returns &lt;code&gt;AtomicReference&amp;lt;Pair&amp;lt;Int, String?&amp;gt;&amp;gt;(0 to null)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;During each visit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the visits count from the accumulator&lt;/li&gt;
&lt;li&gt;Increment it&lt;/li&gt;
&lt;li&gt;Print it&lt;/li&gt;
&lt;li&gt;Store it back in the accumulator&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update the tests accordingly&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With packages &lt;code&gt;ch.frankel.blog.foo&lt;/code&gt;, &lt;code&gt;org.frankel.blog.bar&lt;/code&gt;, and &lt;code&gt;org.frankel.blog.baz&lt;/code&gt;, the log shows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[INFO] [stdout] FlattenStructure: 1 file visited
[INFO] [stdout] FlattenStructure: 2 files visited
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By changing &lt;code&gt;ch.frankel.blog.foo&lt;/code&gt; to &lt;code&gt;org.frankel.blog.foo&lt;/code&gt;, the log changes to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[INFO] [stdout] FlattenStructure: 1 files visited
[INFO] [stdout] FlattenStructure: 2 files visited
[INFO] [stdout] FlattenStructure: 3 files visited
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because I made these changes just to validate my understanding of how OpenRewrite works, I put them in the &lt;code&gt;visits_count&lt;/code&gt; branch on GitHub. To see the differences, execute &lt;code&gt;git diff master visits_count&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;In this post, I added the automatic computation of the root package. I had to change my design and understand how scanning recipes work. Then, I skipped further visits when there wasn't any common root package to optimize performance.&lt;/p&gt;
&lt;p&gt;The recipe is still not serializable, though it's a recommendation. I also noticed that my tests didn't leverage OpenRewrite's testing API. There's still a lot of work to do!&lt;/p&gt;
&lt;p&gt;The complete source code for this post can be found on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ajavageek/flatten-kotlin-recipe&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To go further:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.openrewrite.org/concepts-and-explanations/recipes#scanning-recipes&quot;&gt;Scanning Recipes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/openrewrite/rewrite-testing-frameworks/blob/main/src/main/java/org/openrewrite/java/testing/mockito/AnyToNullable.java&quot;&gt;Code sample of an existing scanning recipe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.openrewrite.org/reference/scanning-recipes&quot;&gt;Existing scanning recipes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.frankel.ch/openrewrite-recipes/2/&quot;&gt;A Java Geek&lt;/a&gt; on June 15&lt;sup&gt;th&lt;/sup&gt;, 2025&lt;/em&gt;&lt;/p&gt;
</description>
<category>Java</category>
<guid isPermaLink="true">https://coderlegion.com/3367/improving-my-previous-openrewrite-recipe</guid>
<pubDate>Thu, 19 Jun 2025 09:26:47 +0000</pubDate>
</item>
</channel>
</rss>