From 3b84e2c2d4d9ac255590946a93a121578705d14c Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Fri, 8 May 2026 21:50:29 +0300 Subject: [PATCH 01/10] Add a blog post about porting code with AI --- ...hat-i-learned-from-porting-code-with-ai.md | 88 ++++++++++++++++++ .../galaxybrain.avif | Bin 0 -> 56035 bytes 2 files changed, 88 insertions(+) create mode 100644 _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md create mode 100644 img/what-i-learned-from-porting-code-with-ai/galaxybrain.avif diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md new file mode 100644 index 00000000..72b6750e --- /dev/null +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -0,0 +1,88 @@ +--- +layout: post +title: "What I Learned from Porting an Astro HTML Generator to Java with AI" +author: jarzka +excerpt: > + Switching technologies mid-project used to be a massive undertaking. With AI agents, it's now possible to port an entire codebase to a different language in a fraction of the time — but it still requires careful planning and human oversight. +tags: + - AI agents + - porting code + - Astro + - Java +--- + +There have probably been many situations where software development teams have made the "wrong" technology choice but couldn't justify switching because they were already too deep in the swamp. Maybe the chosen technology turned out to be hard to maintain, support got dropped mid-project or the choice was simply a poor fit for the problem — but since it works, rewriting it could not be justified and developers just have to live with their choice. + +You might remember from my [earlier blog post](https://dev.solita.fi/2024/12/02/building-static-websites-with-astro.html) that we picked [Astro](https://astro.build/), a Node-based framework, for our static website generator project. Astro was chosen because it felt modern, content-focused, and was easy to pick up for any React developer — just what we needed. While the previous blog post is still relevant and I personally enjoyed using Astro, it ended up being the wrong choice in our project. The main reason was performance: we have strict requirements for site generation speed that Astro just couldn't meet despite multiple optimisation attempts. This created a motivation for us to begin thinking about switching it to something else. + +Switching technologies is a significant change that would traditionally require a lot of work. With today's AI agents, however, the change is possible to carry out in significantly less time, leveraging existing code as much as possible. For AI, an existing codebase with good tests is like precisely written documentation of how a system should work. And since AI has proven to be very efficient in generating working systems from text prompts, it can use the existing implementation as a "prompt" for generating a similar system in another technology. + +## Why Astro Wasn't For Us + +Our practical experience of using Astro for two years revealed that the generation slowness was mainly caused by two factors. First, Astro orchestrates page generation by itself (via `getStaticPaths`) and doesn't really allow fine-grained optimization such as parallel page generation or reading all required data into memory once before generation starts. Second, while splitting HTML code into small Astro components is good for maintainability, we noticed that each additional component slowed the generation. + +We also realized that building a static site in our case didn't necessarily require a dedicated HTML framework. Without a framework, we would get full control over orchestration: how data is read, how generation is parallelized, in which order pages are generated and so on. This led us to abandon frameworks altogether and look for a programming language + templating engine combination instead. We evaluated both Java and Go-based solutions by creating test ports that proved their performance benefits. We chose Java because the rest of our system was already built with it. For a templating engine we picked [JTE](https://jte.gg) for its promise of speed and type safety. + +## Planning the Porting + +I might not have dared to start thinking about AI-assisted code porting without previous experience. Luckily, I had already ported my personal chess game project written in Java over 10 years ago into a web-based version with **GitHub Copilot** (the VS Code extension) and **Claude Opus 4.6** model. The porting was a success, so I was fairly convinced that AI had reached a level where even larger project's technology — several tens of thousands of lines — could be reasonably switched. The experience also gave me some intuition about how to approach a port and what to expect. + +Generative AI is essentially a machine that generates text based on statistics and probabilities. Combine this with an agent that is able to figure things out on its own and you have a chance to get a decent result with a simple prompt like: _"Port this project to Java"_. In practice, however, you'll get much better results by providing good context for the process. For example, it was beneficial for us to write an initial plan on how the Astro-specific page orchestration should look in the Java port and provide the idea as a context for the AI. While AI is also pretty good at solving tooling differences on its own and can ask clarifying questions, it still helps if you make important choices early on. This of course requires sufficient understanding on both the source and target technologies. + +Once the plan is ready, you can give it to the AI as context in Copilot's planning mode for further analysis. In my case, the AI was able to create an execution plan for the order in which things should be ported. For example, domain objects that are used everywhere in the project were clear candidates to port first, since their shape affects the entire codebase. + +## Porting Process + +One challenge during the porting was that I occasionally noticed things going in the wrong direction mid-process. Fortunately, I was able to guide the AI by providing more details ("steering") while it was creating the port. This turned out to be highly worthwhile: if the AI "forgot" to follow my instructions early on, it didn't follow them later either. Fixing issues as early as possible helped steer the whole process in the right direction. + +However, giving steering instructions sometimes caused the AI to stop the porting process entirely after fixing a single issue. I could resume it with a simple _"continue"_ command, but an even better approach turned out to be prefixing my instructions with _"by the way"_, causing the AI to correct its behavior on the fly. This way, I was able to continuously review and guide the AI's work while it was doing its thing. + +## Findings After Initial Porting + +In our case, the actual code porting took a couple of hours. This covers the time from the first prompt to the point where the new generator code compiled successfully. A couple more hours were spent to ensure all the ported tests were green before actually trying to run the generator. + +After AI considered the port _"done"_, I ran it and immediately experienced a multifold speed improvement compared to our original generator! It was a joy to see the first output: the familiar-looking HTML website we had created but this time generated by an entirely different technology. + +At this point, it was clear that the new system was functioning, so it was time to start analysing the quality of the code more closely. Diving deeper into the generated code, we found several issues: + +- **File and folder mapping was confusing**, meaning that there was no clear A-to-B relationship between the source files / folders and the ported system, which made reviewing much harder. In some cases the difference was justified by the architectural differences, but AI had clearly used its own imagination of how the project should be structured even if the original structuring was quite clear. Thus, I ended up starting the whole porting process from scratch and asked the AI to prefer a one-to-one relationship between files and folders when porting code (with some exceptions). +- **Code duplication.** The original codebase had many shared utilities used across the generator. In the ported code, the AI didn't always understand to reuse these utilities and instead created new inline solutions. +- **Unused code.** The AI created various helper functions that it ended up not using at all. This might have been partly caused by architectural differences between the source and target systems. It caused unnecessary confusion since after the port was done, we ended up reviewing and reasoning code that turned out to be dead. +- **Missing or replaced comments.** Important code comments from the original source were often left out. Even worse, the AI sometimes added its own comments explaining how the ported Java code differs from the original TypeScript. Such comments are pointless since the original Astro project will eventually be removed. +- **Non-idiomatic code.** The ported code was technically functional but not particularly "Java-like". For example, the AI had ported our manually written number formatting logic directly from TypeScript to Java even if there was built-in support for such formatting directly in Java. AI just didn't dare to take advantage of it. +- **Accidental bug discovery.** Perhaps the most "entertaining" finding was that the porting process revealed bugs in the original implementation! For example, the original generator was creating a few unnecessary pages with empty content. I noticed this when I was arguing with the AI about generation rules that differed from the original. It turned out the AI had independently changed the rules — not something I knew I wanted, but this time it was actually correct! + +Despite these problems, the most significant finding was that logical errors were almost nonexistent in the ported code: there were only a few and they were easy to fix. It seems that AI is pretty good at porting code between languages and keeping the result functionally equivalent. + +I'm not sure whether addressing every possible issue in the initial prompt would have produced a perfect result. After all, it's easy to tell an AI not to make mistakes, but since porting a system to another technology is a lengthy process, the AI might not "remember" to follow every instruction at every step. Duplication and dead code can also happen by accident when ported code is being refactored. Thus, I believe the initial port — even if working — should only be treated as a starting point towards the final version. + +## Ensuring Code Quality + +Ensuring the quality of such a large-scale ported system is challenging. First and foremost, it's important to understand that the quality of the ported code is highly dependent on the quality of the source system itself. For example, if the source system has good test coverage, you are already at a good starting point. Also, code structuring, method naming and general _feeling_ of the quality will be strongly retained in the ported version. + +To begin analysing the quality of the ported code, we used static code analysis tools to verify that the ported generator did not break our coding rules. We also used a diffing tool to ensure the new generator produced identical output to the original generator with the same input data (not counting irrelevant details like formatting differences). Luckily, fixing little things here and there also forced me to check whether I could make sense of the AI-ported code as a human developer. + +We slowly progressed towards the point in which classic rules-based tools stopped finding issues, diffing showed no change in functionality and I could not find problems by reading the new code. Still, I wasn't confident that there were no more problems to find. So I began thinking... + +![Expanding brain meme](/img/what-i-learned-from-porting-code-with-ai/galaxybrain.avif) + +Yes: it was time to use AI to analyze its own code! While I do not recommend blindly trusting AI in this process, I believe it can reveal interesting findings that other methods might miss. + +While a simple prompt like _"Review this ported code"_ could produce desired results, I found that giving a specific angle for the analysis provided much better results. Here are some of the prompts that provided good results when run multiple times with different agents. Even better results can be obtained by focusing the analysis on a specific part of the ported codebase. + +- _"This project was ported from Astro to Java. Can you find duplicates that could use shared helpers?"_ +- _"... Can you find unused code or code that is only used in tests?"_ +- _"... Can you find functionality that could be implemented more idiomatically in Java?"_ +- _"... Can you find security issues in the ported implementation?"_ +- _"... Can you find any functional differences between the original and ported implementation that could cause different output?"_ +- _"... Can you find places where null handling differs between the original TypeScript and the ported Java, such as unchecked nulls?"_ + +Despite all the automated code reviewing, I feel that human code review still remains highly necessary. After all, the main purpose of programming languages is not to tell a computer what to do, but to tell _another human_ what a computer should do. Thus, the ported code is not usable if a human cannot understand it. + +We manually reviewed the ported code until our feeling was strong enough that the result was merge-ready. When an issue was found, we investigated whether a similar issue was also found elsewhere in the ported codebase (with and without the help of AI) and very often found multiple things to fix. This helped create certainty that most of our findings were fixed throughout the whole ported codebase, not just in a single file. + +## Conclusion + +One of the most significant shifts brought by agentic AI is that "wrong" technology choices no longer have to be permanent. What previously meant months of expensive rewriting can now be approached as a structured, AI-assisted process with significantly less time. Choices and their reasoning still matter, but the fear of being locked into a suboptimal stack is considerably smaller than it used to be. + +That said, AI-assisted porting is not a single-step operation. Badly written system won't automatically become better if ported to another technology. Also, the initial port will almost certainly contain duplication, dead code, missing documentation, and non-idiomatic patterns. Treating the first working version as a foundation to improve upon is the right mindset. Good upfront context, active steering during the process, and thorough quality assurance afterwards — combining static analysis, diffing, targeted AI review, and human code review — are all necessary ingredients for a good end result. diff --git a/img/what-i-learned-from-porting-code-with-ai/galaxybrain.avif b/img/what-i-learned-from-porting-code-with-ai/galaxybrain.avif new file mode 100644 index 0000000000000000000000000000000000000000..629e0ea4edbfa5b86acf8a2344ae5a2400c3ffbe GIT binary patch literal 56035 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 Date: Mon, 11 May 2026 10:58:01 +0300 Subject: [PATCH 02/10] Use wider terms than just AI --- ...05-11-what-i-learned-from-porting-code-with-ai.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index 72b6750e..554b2a31 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -25,17 +25,19 @@ We also realized that building a static site in our case didn't necessarily requ ## Planning the Porting -I might not have dared to start thinking about AI-assisted code porting without previous experience. Luckily, I had already ported my personal chess game project written in Java over 10 years ago into a web-based version with **GitHub Copilot** (the VS Code extension) and **Claude Opus 4.6** model. The porting was a success, so I was fairly convinced that AI had reached a level where even larger project's technology — several tens of thousands of lines — could be reasonably switched. The experience also gave me some intuition about how to approach a port and what to expect. +I might not have dared to start thinking about AI-assisted code porting without previous experience. Luckily, I had already ported my personal chess game project written in Java over 10 years ago into a web-based version. The porting was a success, so I was fairly convinced that AI had reached a level where even larger project's technology — several tens of thousands of lines — could be reasonably switched. The experience also gave me some intuition about how to approach a port and what to expect. -Generative AI is essentially a machine that generates text based on statistics and probabilities. Combine this with an agent that is able to figure things out on its own and you have a chance to get a decent result with a simple prompt like: _"Port this project to Java"_. In practice, however, you'll get much better results by providing good context for the process. For example, it was beneficial for us to write an initial plan on how the Astro-specific page orchestration should look in the Java port and provide the idea as a context for the AI. While AI is also pretty good at solving tooling differences on its own and can ask clarifying questions, it still helps if you make important choices early on. This of course requires sufficient understanding on both the source and target technologies. +To begin code porting with AI, you need two things: An LLM (Large Language Model), which is essentially a machine that is used to generate text based on statistics and probabilities, and an AI agent that is able to figure things out on its own by using the LLM. In my case, the tool of choise was **GitHub Copilot** (the VS Code extension with agentic capabilities) and **Claude Opus 4.6** model. -Once the plan is ready, you can give it to the AI as context in Copilot's planning mode for further analysis. In my case, the AI was able to create an execution plan for the order in which things should be ported. For example, domain objects that are used everywhere in the project were clear candidates to port first, since their shape affects the entire codebase. +In theory, you have a chance to get a decent result with a simple prompt like: _"Port this project to Java"_. In practice, however, you'll get much better results by providing good context for the process. For example, it was beneficial for us to write an initial plan on how the Astro-specific page orchestration should look in the Java port and provide the idea as a context for the AI. While AI is also pretty good at solving tooling differences on its own and can ask clarifying questions, it still helps if you make important choices early on. This of course requires sufficient understanding on both the source and target technologies. + +Once the plan is ready, you can give it to the AI as context in Copilot's planning mode for further analysis. In my case, the AI agent was able to create an execution plan for the order in which things should be ported. For example, domain objects that are used everywhere in the project were clear candidates to port first, since their shape affects the entire codebase. ## Porting Process -One challenge during the porting was that I occasionally noticed things going in the wrong direction mid-process. Fortunately, I was able to guide the AI by providing more details ("steering") while it was creating the port. This turned out to be highly worthwhile: if the AI "forgot" to follow my instructions early on, it didn't follow them later either. Fixing issues as early as possible helped steer the whole process in the right direction. +One challenge during the porting was that I occasionally noticed things going in the wrong direction mid-process. Fortunately, I was able to guide the agent by providing more details ("steering") while it was creating the port. This turned out to be highly worthwhile: if the AI "forgot" to follow my instructions early on, it didn't follow them later either. Fixing issues as early as possible helped steer the whole process in the right direction. -However, giving steering instructions sometimes caused the AI to stop the porting process entirely after fixing a single issue. I could resume it with a simple _"continue"_ command, but an even better approach turned out to be prefixing my instructions with _"by the way"_, causing the AI to correct its behavior on the fly. This way, I was able to continuously review and guide the AI's work while it was doing its thing. +However, giving steering instructions sometimes caused the agent to stop the porting process entirely after fixing a single issue. I could resume it with a simple _"continue"_ command, but an even better approach turned out to be prefixing my instructions with _"by the way"_, causing the agent to correct its behavior on the fly. This way, I was able to continuously review and guide the AI's work while it was doing its thing. ## Findings After Initial Porting From fc4f9451ff28bd951e4d346a15b359c7f00b42bb Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 11:18:47 +0300 Subject: [PATCH 03/10] Add a few words about ported tests --- _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index 554b2a31..bc9d100e 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -81,7 +81,9 @@ While a simple prompt like _"Review this ported code"_ could produce desired res Despite all the automated code reviewing, I feel that human code review still remains highly necessary. After all, the main purpose of programming languages is not to tell a computer what to do, but to tell _another human_ what a computer should do. Thus, the ported code is not usable if a human cannot understand it. -We manually reviewed the ported code until our feeling was strong enough that the result was merge-ready. When an issue was found, we investigated whether a similar issue was also found elsewhere in the ported codebase (with and without the help of AI) and very often found multiple things to fix. This helped create certainty that most of our findings were fixed throughout the whole ported codebase, not just in a single file. +As mentioned previously, our plan was to import not only the generator code itself, but also its tests. Some tests were written in Playwright which did not need porting at all since we could just run them normally against the new generated output. Still, there were many unit tests originally written in TypeScript for Vitest and now ported to Java that obviously needed human observation. Green tests mean nothing if they do not test the right things. + +We manually reviewed the ported code and its tests until our feeling was strong enough that the result was merge-ready. When an issue was found, we investigated whether a similar issue was also found elsewhere in the ported codebase (with and without the help of AI) and very often found multiple things to fix. This helped create certainty that most of our findings were fixed throughout the whole ported codebase, not just in a single file. ## Conclusion From fdfe62586873636e474ce6ae352350db9fc2f9a2 Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 11:57:39 +0300 Subject: [PATCH 04/10] Add a couple of words about the end result --- ...2026-05-11-what-i-learned-from-porting-code-with-ai.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index bc9d100e..e13b38b7 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -54,7 +54,7 @@ At this point, it was clear that the new system was functioning, so it was time - **Non-idiomatic code.** The ported code was technically functional but not particularly "Java-like". For example, the AI had ported our manually written number formatting logic directly from TypeScript to Java even if there was built-in support for such formatting directly in Java. AI just didn't dare to take advantage of it. - **Accidental bug discovery.** Perhaps the most "entertaining" finding was that the porting process revealed bugs in the original implementation! For example, the original generator was creating a few unnecessary pages with empty content. I noticed this when I was arguing with the AI about generation rules that differed from the original. It turned out the AI had independently changed the rules — not something I knew I wanted, but this time it was actually correct! -Despite these problems, the most significant finding was that logical errors were almost nonexistent in the ported code: there were only a few and they were easy to fix. It seems that AI is pretty good at porting code between languages and keeping the result functionally equivalent. +Despite these problems, the most significant finding was that logical errors were almost nonexistent in the ported generator code: there were only a few and they were easy to fix. It seems that AI is pretty good at porting code between languages and keeping the result functionally equivalent. Also our Astro components, which are essentially simple HTML templates, were cleanly converted to JTE code without any major issues. I'm not sure whether addressing every possible issue in the initial prompt would have produced a perfect result. After all, it's easy to tell an AI not to make mistakes, but since porting a system to another technology is a lengthy process, the AI might not "remember" to follow every instruction at every step. Duplication and dead code can also happen by accident when ported code is being refactored. Thus, I believe the initial port — even if working — should only be treated as a starting point towards the final version. @@ -85,6 +85,12 @@ As mentioned previously, our plan was to import not only the generator code itse We manually reviewed the ported code and its tests until our feeling was strong enough that the result was merge-ready. When an issue was found, we investigated whether a similar issue was also found elsewhere in the ported codebase (with and without the help of AI) and very often found multiple things to fix. This helped create certainty that most of our findings were fixed throughout the whole ported codebase, not just in a single file. +## Verdict + +The main motivation of switching the generator's technology was to gain performance benefits that were simply impossible to get with the old implementation. This goal was clearly reached, so the only question remains: did we manage to create a port that’s just as high-quality as if it had been written from scratch? + +As mentioned, there were numerous small problems in the first version of the ported code. The initial port was ready in a single work day, but cleaning, refactoring and reviewing the whole thing took a couple of weeks of work. Despite this, the AI-generated code was still a much better starting point than trying to port everything manually. I'm quite happy with the end result we got with this approach. + ## Conclusion One of the most significant shifts brought by agentic AI is that "wrong" technology choices no longer have to be permanent. What previously meant months of expensive rewriting can now be approached as a structured, AI-assisted process with significantly less time. Choices and their reasoning still matter, but the fear of being locked into a suboptimal stack is considerably smaller than it used to be. From 0df683e998195c6c9624abf27a3e937856484a5f Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 11:58:20 +0300 Subject: [PATCH 05/10] Fix a minor typo --- _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index e13b38b7..0fe574a6 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -51,7 +51,7 @@ At this point, it was clear that the new system was functioning, so it was time - **Code duplication.** The original codebase had many shared utilities used across the generator. In the ported code, the AI didn't always understand to reuse these utilities and instead created new inline solutions. - **Unused code.** The AI created various helper functions that it ended up not using at all. This might have been partly caused by architectural differences between the source and target systems. It caused unnecessary confusion since after the port was done, we ended up reviewing and reasoning code that turned out to be dead. - **Missing or replaced comments.** Important code comments from the original source were often left out. Even worse, the AI sometimes added its own comments explaining how the ported Java code differs from the original TypeScript. Such comments are pointless since the original Astro project will eventually be removed. -- **Non-idiomatic code.** The ported code was technically functional but not particularly "Java-like". For example, the AI had ported our manually written number formatting logic directly from TypeScript to Java even if there was built-in support for such formatting directly in Java. AI just didn't dare to take advantage of it. +- **Non-idiomatic code.** The ported code was technically functional but not always "Java-like". For example, the AI had ported our manually written number formatting logic directly from TypeScript to Java even if there was built-in support for such formatting directly in Java. AI just didn't dare to take advantage of it. - **Accidental bug discovery.** Perhaps the most "entertaining" finding was that the porting process revealed bugs in the original implementation! For example, the original generator was creating a few unnecessary pages with empty content. I noticed this when I was arguing with the AI about generation rules that differed from the original. It turned out the AI had independently changed the rules — not something I knew I wanted, but this time it was actually correct! Despite these problems, the most significant finding was that logical errors were almost nonexistent in the ported generator code: there were only a few and they were easy to fix. It seems that AI is pretty good at porting code between languages and keeping the result functionally equivalent. Also our Astro components, which are essentially simple HTML templates, were cleanly converted to JTE code without any major issues. From 5be90bf565b587aeae3ed69338e4157813fbd021 Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 12:09:15 +0300 Subject: [PATCH 06/10] Improve Verdict --- _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index 0fe574a6..51469a6f 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -89,7 +89,7 @@ We manually reviewed the ported code and its tests until our feeling was strong The main motivation of switching the generator's technology was to gain performance benefits that were simply impossible to get with the old implementation. This goal was clearly reached, so the only question remains: did we manage to create a port that’s just as high-quality as if it had been written from scratch? -As mentioned, there were numerous small problems in the first version of the ported code. The initial port was ready in a single work day, but cleaning, refactoring and reviewing the whole thing took a couple of weeks of work. Despite this, the AI-generated code was still a much better starting point than trying to port everything manually. I'm quite happy with the end result we got with this approach. +As mentioned, there were numerous small problems in the first version of the ported code. The initial port was ready in a single work day, but cleaning, refactoring, testing and reviewing the whole thing took a couple of weeks of work. Despite this, the AI-generated code was still a much better starting point than trying to port everything manually. Of course, it would have been best to implement it this way right from the start, but I'm quite happy with the end result we got by porting the existing code with AI. ## Conclusion From 98033924caa24963d27e8bffefe9d7f7eb46af92 Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 12:11:00 +0300 Subject: [PATCH 07/10] Improve Astro sentence --- _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index 51469a6f..66dfe4aa 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -54,7 +54,7 @@ At this point, it was clear that the new system was functioning, so it was time - **Non-idiomatic code.** The ported code was technically functional but not always "Java-like". For example, the AI had ported our manually written number formatting logic directly from TypeScript to Java even if there was built-in support for such formatting directly in Java. AI just didn't dare to take advantage of it. - **Accidental bug discovery.** Perhaps the most "entertaining" finding was that the porting process revealed bugs in the original implementation! For example, the original generator was creating a few unnecessary pages with empty content. I noticed this when I was arguing with the AI about generation rules that differed from the original. It turned out the AI had independently changed the rules — not something I knew I wanted, but this time it was actually correct! -Despite these problems, the most significant finding was that logical errors were almost nonexistent in the ported generator code: there were only a few and they were easy to fix. It seems that AI is pretty good at porting code between languages and keeping the result functionally equivalent. Also our Astro components, which are essentially simple HTML templates, were cleanly converted to JTE code without any major issues. +Despite these problems, the most significant finding was that logical errors were almost nonexistent in the ported generator code: there were only a few and they were easy to fix. It seems that AI is pretty good at porting code between languages and keeping the result functionally equivalent. Also our Astro components, which range from simple to more complex HTML templates, were cleanly converted to JTE code without any major issues. I'm not sure whether addressing every possible issue in the initial prompt would have produced a perfect result. After all, it's easy to tell an AI not to make mistakes, but since porting a system to another technology is a lengthy process, the AI might not "remember" to follow every instruction at every step. Duplication and dead code can also happen by accident when ported code is being refactored. Thus, I believe the initial port — even if working — should only be treated as a starting point towards the final version. From 5810dd1c71ff063491755bceb5b53c1804a6a3b6 Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 12:13:59 +0300 Subject: [PATCH 08/10] Add one sentence about the results of the ported tests --- _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index 66dfe4aa..4c85c524 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -81,7 +81,7 @@ While a simple prompt like _"Review this ported code"_ could produce desired res Despite all the automated code reviewing, I feel that human code review still remains highly necessary. After all, the main purpose of programming languages is not to tell a computer what to do, but to tell _another human_ what a computer should do. Thus, the ported code is not usable if a human cannot understand it. -As mentioned previously, our plan was to import not only the generator code itself, but also its tests. Some tests were written in Playwright which did not need porting at all since we could just run them normally against the new generated output. Still, there were many unit tests originally written in TypeScript for Vitest and now ported to Java that obviously needed human observation. Green tests mean nothing if they do not test the right things. +As mentioned previously, our plan was to import not only the generator code itself, but also its tests. Some tests were written in Playwright which did not need porting at all since we could just run them normally against the new generated output. Still, there were many unit tests originally written in TypeScript for Vitest and now ported to Java that obviously needed human observation. Green tests mean nothing in the end if they do not test the right things. Luckily, AI had been quite clever also when porting test code — most of the needed fixes were related to using common patterns in the test code. We manually reviewed the ported code and its tests until our feeling was strong enough that the result was merge-ready. When an issue was found, we investigated whether a similar issue was also found elsewhere in the ported codebase (with and without the help of AI) and very often found multiple things to fix. This helped create certainty that most of our findings were fixed throughout the whole ported codebase, not just in a single file. From 4c002811b0e93105e38b47b31b9d47779197bc83 Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 12:20:40 +0300 Subject: [PATCH 09/10] Improve grammar --- _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index 4c85c524..43c4f65a 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -89,7 +89,7 @@ We manually reviewed the ported code and its tests until our feeling was strong The main motivation of switching the generator's technology was to gain performance benefits that were simply impossible to get with the old implementation. This goal was clearly reached, so the only question remains: did we manage to create a port that’s just as high-quality as if it had been written from scratch? -As mentioned, there were numerous small problems in the first version of the ported code. The initial port was ready in a single work day, but cleaning, refactoring, testing and reviewing the whole thing took a couple of weeks of work. Despite this, the AI-generated code was still a much better starting point than trying to port everything manually. Of course, it would have been best to implement it this way right from the start, but I'm quite happy with the end result we got by porting the existing code with AI. +As mentioned, there were numerous small problems in the first version of the ported code. The initial port was ready in a single work day, but cleaning, refactoring, testing and reviewing the whole thing took a couple of weeks of work. Despite this, the AI-generated code was still a much better starting point than trying to re-create the whole thing manually from scratch. Of course, it would have been best to implement it this way right from the start, but I'm quite happy with the end result we got by porting the existing code with AI. ## Conclusion From 0ebf73b3142c83acc7379e588f0014a74279262c Mon Sep 17 00:00:00 2001 From: Jari Hanhela Date: Mon, 11 May 2026 12:51:05 +0300 Subject: [PATCH 10/10] Fix typo --- _posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md index 43c4f65a..11c0843e 100644 --- a/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md +++ b/_posts/2026-05-11-what-i-learned-from-porting-code-with-ai.md @@ -27,7 +27,7 @@ We also realized that building a static site in our case didn't necessarily requ I might not have dared to start thinking about AI-assisted code porting without previous experience. Luckily, I had already ported my personal chess game project written in Java over 10 years ago into a web-based version. The porting was a success, so I was fairly convinced that AI had reached a level where even larger project's technology — several tens of thousands of lines — could be reasonably switched. The experience also gave me some intuition about how to approach a port and what to expect. -To begin code porting with AI, you need two things: An LLM (Large Language Model), which is essentially a machine that is used to generate text based on statistics and probabilities, and an AI agent that is able to figure things out on its own by using the LLM. In my case, the tool of choise was **GitHub Copilot** (the VS Code extension with agentic capabilities) and **Claude Opus 4.6** model. +To begin code porting with AI, you need two things: An LLM (Large Language Model), which is essentially a machine that is used to generate text based on statistics and probabilities, and an AI agent that is able to figure things out on its own by using the LLM. In my case, the tool of choice was **GitHub Copilot** (the VS Code extension with agentic capabilities) and **Claude Opus 4.6** model. In theory, you have a chance to get a decent result with a simple prompt like: _"Port this project to Java"_. In practice, however, you'll get much better results by providing good context for the process. For example, it was beneficial for us to write an initial plan on how the Astro-specific page orchestration should look in the Java port and provide the idea as a context for the AI. While AI is also pretty good at solving tooling differences on its own and can ask clarifying questions, it still helps if you make important choices early on. This of course requires sufficient understanding on both the source and target technologies.