Porting code to another language with AI: migration plan and checks
AI translates code between languages quickly, but it will not preserve behaviour on its own. A module migration plan with reference tests and comparison.
5 min · · development · автоматизация
You can rewrite code in another language with AI in hours instead of weeks, but only if you decided in advance how you will prove that the new code does the same thing. A model translates syntax well, while implicit behaviour (rounding, iteration order, handling of empty values, encodings) gets lost easily. Below is a migration plan for one module: inventory, reference tests, piece-by-piece porting, behaviour comparison and a cost estimate. As an example we take an invoice-parsing utility in Python that we port to Go.
Step 1. Inventory
Before the first request to the model, list what you are porting. You can build the list together with the model, but you verify it:
- the public interface: functions called from outside, their arguments and return values;
- dependencies: third-party libraries and what replaces them in the target language;
- side effects: files, network, database, environment variables, time;
- places with "odd" behaviour: regular expressions, dates and money, sorting, rounding, unicode.
The last item is the most important. That is where languages diverge: integer division, handling of None and nil, dictionary key order, overflow behaviour. Ask the model to find such places and list them as risks, then go through the list yourself.
Step 2. Reference tests before the port
The migration rule: tests are written on the old code and before the port. If there are no tests, create "golden" data: run the old implementation on representative inputs and save the results.
import json # make_golden.py: run on the OLD code
from pathlib import Path
from billing.parser import parse_invoice # old implementation
cases = []
for path in sorted(Path("samples").glob("*.txt")):
text = path.read_text(encoding="utf-8")
cases.append({"name": path.name, "input": text, "output": parse_invoice(text)})
Path("golden.json").write_text(
json.dumps(cases, ensure_ascii=False, indent=2, sort_keys=True),
encoding="utf-8",
)The samples should include edge cases: an empty file, a very long line, Cyrillic, a wrong format, zero and negative amounts. The model can help invent such inputs, but take the result of the run only from the real old implementation.
Step 3. Port in pieces
Do not ask "rewrite the whole project". Move bottom-up through the dependency graph: pure functions without side effects first, then modules with input and output, the entry point last. For each piece:
- Give the model the function's source, its tests and the list of risks for it.
- Ask for idiomatic code in the target language, not a line-by-line translation: an indexed loop is written differently in Python and Go.
- Ask it to list separately the places where behaviour may differ.
- Run the reference tests. Analyse failures with the model, showing it the real output.
An agent that runs the tests itself is convenient for all this: Claude Code or another tool of that kind. Then the "edit, run, error, edit" loop does not need your involvement, and you read the final diff.
Step 4. Behaviour comparison
Reference files are not enough. Add a comparison of the two implementations on the same inputs, including random ones:
import json # compare.py: runs old and new implementations on the same data
import subprocess
from pathlib import Path
def run_old(text: str) -> object:
from billing.parser import parse_invoice
return parse_invoice(text)
def run_new(text: str) -> object:
p = subprocess.run(
["./invoice-go"], input=text, capture_output=True,
text=True, encoding="utf-8", timeout=10,
)
return json.loads(p.stdout)
bad = 0
for path in sorted(Path("samples").glob("*.txt")):
text = path.read_text(encoding="utf-8")
if run_old(text) != run_new(text):
bad += 1
print("MISMATCH:", path.name)
print("mismatches:", bad)Compare parsed structures, not strings, and decide separately what counts as equal: key order, floating-point formatting, whitespace. Every mismatch is either a porting error or a bug in the old code that you have now found. The second happens too, and a human decides whether to repeat the bug or fix it.
Step 5. What not to trust without checking
- Multithreading and concurrency. A model easily reproduces data races, especially when porting synchronous code to asynchronous.
- Money and time. Check types:
floatinstead of decimals, default time zones. - Error handling. An exception in Python, a returned value in Go, a
Resultin Rust; make sure an error is not lost silently. - Dependencies. A model can "find" a library that does not exist. Verify each dependency you add in the package registry.
- Performance. A correct port does not guarantee a fast one; measure.
What it costs
The estimate is based on code size. Assumptions (illustrative, check on your project): a 12,000-line module, about 10 tokens per line, so roughly 0.12M tokens. The exact number comes from token counting: it runs no generation, but the request itself is paid, with tokens billed at the model's input price.
One pass (read the source and write the translation) without cache: input 0.12M × input price + output 0.12M × output price. Zukko prices as of October 18, 2026:
claude-sonnet-5-5: 0.12 × 0.4 + 0.12 × 2 = 0.048 + 0.24 = $0.288.claude-opus-5-5: 0.12 × 0.8 + 0.12 × 4 = 0.096 + 0.48 = $0.576.gpt-6.1-sol: 0.12 × 0.27 + 0.12 × 1.32 = 0.0324 + 0.1584 = $0.191.
The real sum is larger: add tests, fixes and reruns. If you assume three or four iterations per piece, multiply by three or four. Agent tools add the accumulated context on every step, so it helps to estimate the budget in advance and set a limit on the key. For porting a large project, start with one module, look at the actual spend in the "Usage" section of the dashboard and only then scale. See also: how to reduce token spend.
We cannot say in advance which model will handle your stack better. Strength index in our catalog: Opus 5.5 is 58, GPT-6.1 Sol is 52, Sonnet 5.5 is 48. Take one function of medium complexity, run it through two or three models and compare by the reference tests.
Connecting
Any SDK works for scripts: create a key in the dashboard, use the base URL https://api.zukko.pro/v1 for an OpenAI-compatible client, and the address without /v1 for Claude Code and the Anthropic SDK. Access from Russia goes through the gateway; payment by SBP, MasterCard card or cryptocurrency.
FAQ
Can I rewrite a whole project in one request?
Technically a context of up to 1M tokens in most catalog models fits a lot of code, but that does not improve verification. It is more reliable to port in pieces and check each with reference tests.
Will an AI code generation tool check by itself that the code works?
The model itself will not: it does not run code. Tests and agent tools that execute commands do that. That is why you need tests before the port.
What if the project has no tests?
Create golden data from the old implementation (step 2). It does not replace full tests, but it fixes the current behaviour, and preserving it is exactly the goal of the migration.
Which language is easiest to port?
It depends on the pair. Languages close in paradigm port more easily. But real difficulty is more often in libraries and implicit quirks than in syntax, so judge by the inventory.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Translating subtitles with a neural network: SRT without broken timecodesThe main risk when translating SRT with a neural network is a broken file structure. A scheme where timecodes never reach the model, and a Python script with validation.
- Editing texts with a neural network: proofreading, style and cuttingDifferent kinds of editing need different prompts. Wording for proofreading, style and cutting, how to get edits with explanations and how not to lose the author's voice.
- A neural network for writing scripts: Bash, PowerShell and PythonA model writes scripts for routine fast, but running them blindly is risky. How to state the task, demand a safe dry-run mode and check the result before launch.
- Dify in Docker: installation, first-run setup and connecting modelsInstall Dify with Docker Compose, create the admin account and connect Claude, GPT and DeepSeek models on one key. Plus what to do when containers will not start.