Code migrations, các dự án chuyển đổi mã nguồn sản xuất sang ngôn ngữ mới, từng là những công trình kéo dài nhiều năm cho đến gần đây.
Trong tháng qua, các nhà phát triển cá nhân tại Anthropic đã di chuyển 10 gói mã bao gồm hàng chục đến hàng trăm nghìn dòng mã bằng cách sử dụng Claude Fable 5, Claude Opus 4.8, và các luồng công việc động.
Jarred Sumner (@jarredsumner), đồng sáng lập Bun và Thành viên Ban Kỹ thuật tại Anthropic, đã sử dụng Claude Code để di chuyển Bun từ Zig sang Rust. Một triệu dòng mã đã được tạo ra trong vòng chưa đầy hai tuần, với 100% bộ kiểm thử hiện có của Bun vượt qua CI trước khi merge. 19 lỗi hồi quy xuất hiện sau khi merge và đã được sửa hết. Bản port Rust đã được phát hành trong Claude Code vào tháng Sáu.
Mike Krieger (@mikeyk), đồng trưởng nhóm Anthropic Labs, đã di chuyển một mã nguồn Python sang 165.000 dòng TypeScript trong một ngày cuối tuần. Điều này bao gồm hàng trăm agent, tám phase gate, ba vòng đánh giá đối nghịch, và một kiểm tra tính tương đồng cuối cùng so sánh từng đầu ra lệnh với bản gốc Python.
Khả năng mới của Claude Code thay đổi cách tính toán cho những dự án bị trì hoãn lâu nay. Dưới đây là quy trình sáu bước chúng tôi hiện sử dụng, rút ra từ những gì các cuộc di chuyển này đã dạy chúng tôi.
Hiểu biết cốt lõi là bạn không sửa mã. Bạn sửa quy trình (vòng lặp) đã tạo ra mã đó.
Tại sao và khi nào nên di chuyển ngôn ngữ
Các nhóm khởi động di chuyển vì những thay đổi về bối cảnh giữa lần xây dựng ban đầu và dự án hiện tại. Hoặc là một sự đánh đổi đã biết đang trở nên hạn chế, một cách tiếp cận tốt hơn đã xuất hiện, hoặc hệ sinh thái ban đầu đang thu hẹp lại.
Ví dụ, Jarred ban đầu chọn Zig vì nó mang lại hiệu suất ngang C với sự đơn giản triệt để, lý tưởng cho một người sáng lập duy nhất "viết Bun trong 1 năm tại một căn hộ chật hẹp ở Oakland thời kỳ tiền LLM." Sự đơn giản này đi kèm với những đánh đổi đã biết, mà anh ấy viết về ở đây.
CLI của Bun nhận được hơn 10 triệu lượt tải xuống hàng tháng và được sử dụng rộng rãi trong Claude Code.
Cho đến tận quý trước, những đánh đổi đó sẽ không đủ để biện minh cho việc đóng băng lộ trình phát triển và cam kết nguồn lực cho một dự án kéo dài nhiều quý. Bạn có thể duy trì hai mã nguồn song song trong nhiều quý hoặc nhiều năm, và nếu kết quả cuối cùng chỉ đạt 90% tương đồng, bạn sẽ có một cơn đau đầu lớn hơn lúc bắt đầu.
Giờ đây, kịch bản xấu nhất là bạn xóa nhánh và thử lại.
Tuy nhiên, vẫn cần có một cơ sở kinh doanh hợp lý. Mặc dù việc di chuyển hàng triệu dòng mã không còn tiêu tốn 3 đến 4 triệu đô la chi phí kỹ thuật trong suốt bốn năm dự án, nhưng chúng vẫn tiêu tốn hàng chục đến hàng trăm nghìn đô la hoặc hơn để thực hiện. Ví dụ, quá trình di chuyển Bun đã tiêu thụ 5,9 tỷ token đầu vào chưa được lưu vào bộ nhớ đệm và 690 triệu token đầu ra — khoảng 165.000 đô la theo giá API. Phần chính của bản port của Mike là 27 triệu token.

PR triệu dòng của Jarred.
Tuy nhiên, trường hợp di chuyển không còn cần phải mang tính sống còn nữa. Một năm vá lỗi bộ nhớ trong nhật ký thay đổi, hoặc một điểm nghẽn mãn tính, giờ đây có thể biện minh cho nó.
Bước biên dịch là động lực cho dự án của Mike. Công cụ nội bộ mà nhóm của anh ấy làm việc được phân phối đến người dùng dưới dạng một tệp nhị phân duy nhất. Việc tạo ra tệp nhị phân đó với chuỗi công cụ Python mất khoảng tám phút cho mỗi nền tảng, tổng cộng 30 phút chờ đợi trên toàn bộ ma trận xây dựng cho mỗi lần phát hành. Sau khi port, quá trình biên dịch tương tự hiện chỉ mất khoảng hai giây, tệp nhị phân khởi động nhanh hơn 6 lần, và nhóm đã có thể loại bỏ một đường ống triển khai riêng biệt.
Tại sao AI thay đổi cách tính toán di chuyển mã
Fable và Opus 4.8 đặc biệt giỏi trong việc ủy quyền, chỉ đạo và xác minh các luồng công việc song song với các sub-agent trong khi tìm ra nhiều hướng để đạt được các mục tiêu đã nêu.
Việc di chuyển mã quy mô lớn là một trường hợp sử dụng đặc biệt hiệu quả cho các mô hình tiên tiến này vì:
- Công việc có tính song song. Công việc có thể được thực thi trên hàng nghìn đơn vị độc lập như tệp và crate, vì vậy các agent có thể làm việc cùng lúc thay vì chờ đợi lẫn nhau.
- Ngữ cảnh rõ ràng và toàn diện. Mã cũ đóng vai trò là đặc tả tuyệt vời cho mô hình.
- Có trọng tài tích hợp sẵn. Nhiều mã nguồn lớn sẽ bao gồm một bộ kiểm thử mà các agent có thể sử dụng để xác minh công việc của mình.
- Hàng đợi tự viết. Khi trình biên dịch hoặc quá trình chạy thử thất bại, đó sẽ trở thành mục tiếp theo để agent sửa.
- Chúng yêu cầu tính nhất quán và xử lý các trường hợp ngoại lệ: người đánh giá trích dẫn quy tắc đằng sau mỗi phát hiện, vì vậy vi phạm trở thành một mục trong hàng đợi thay vì một sự khác biệt thầm lặng.
Sáu bước cho việc di chuyển mã quy mô lớn
Để biết thêm chi tiết, bạn có thể đọc blog của Jarred.
Điều kiện tiên quyết
Điều kiện tiên quyết trước khi bắt đầu dự án di chuyển của bạn là phải có một "thẩm phán" mạnh mẽ, nếu không bạn sẽ không có điều kiện thoát hoặc thước đo thành công.
Để xây dựng thẩm phán này:
- Phân loại các kiểm thử hiện có. Sử dụng Claude để xác định kiểm thử nào có thể biểu diễn dưới dạng lời gọi bên ngoài và kiểm thử nào phụ thuộc vào các phần nội bộ sẽ không được port.
- Viết lại để có tính di động. Chuyển đổi các kiểm thử hướng ra bên ngoài thành các khẳng định có thể chạy được trên cả bản gốc và bản port. Sử dụng các agent đối nghịch để xác minh các kiểm thử đã viết lại không làm yếu các khẳng định.
- Xác thực thẩm phán. Chạy nó trên mã gốc để xác nhận nó vượt qua. Sau đó chạy nó trên mã cố tình bị hỏng để xác nhận nó thất bại — một thẩm phán không phát hiện ra lỗi không phải là thẩm phán.
Điều này chủ yếu tuân theo phương pháp của Jarred, với các đánh giá và cổng ở mỗi giai đoạn. Mike đã tuân theo một cấu trúc tổng thể tương tự bằng cách sử dụng các luồng công việc vòng lặp tương tự, nhưng anh ấy đã chạy toàn bộ quá trình di chuyển từ đầu đến cuối, sửa đổi các quy tắc và luồng công việc dựa trên kết quả, và chạy lại — loại bỏ đầu ra mỗi lần cho đến lần chạy thứ ba.

Bước 1 — Tạo sổ tay quy tắc, bản đồ phụ thuộc và kiểm kê khoảng trống
Thứ tự rất quan trọng: sổ tay quy tắc phải đến trước kiểm kê khoảng trống. Kiểm kê khoảng trống được xác định bởi những gì các mặc định của sổ tay quy tắc không bao phủ, và cả hai được kiểm tra cùng nhau trong một cuộc kiểm toán chung.
Sổ tay quy tắc
Hình dạng chính xác của sổ tay quy tắc phụ thuộc vào các quyết định kiến trúc chính mà bạn phải đưa ra ngay từ đầu. Quan trọng nhất trong số đó, liệu mã mới sẽ tuân theo cùng một cấu trúc, hay nó sẽ được thiết kế lại hoàn toàn.
Nếu là trường hợp trước (Jarred), sổ tay quy tắc sẽ chủ yếu là các bảng tra cứu dịch các kiểu và thành ngữ giữa các ngôn ngữ trong khi chỉ vào kiểm kê khoảng trống cho các thành phần khó dịch hơn. Nếu là trường hợp sau (Mike), nó sẽ là một tài liệu thiết kế.
Jarred đã tạo sổ tay quy tắc của mình bằng cách trò chuyện với Claude, hình thành một chính sách cho từng lĩnh vực không rõ ràng. Anh ấy cũng sử dụng tám sub-agent được thiết kế đặc biệt để đánh giá cho 8 loại chế độ lỗi phổ biến khác nhau dựa trên trực giác của chính mình.
Bản đồ phụ thuộc
Bạn cần hiểu các phụ thuộc của tệp để phân chia hiệu quả các luồng công việc cho một quá trình di chuyển song song, để bạn biết tệp nào cần di chuyển trước và tệp nào nên chứa trong cùng một đợt. Claude Code có thể triển khai các agent để tạo và chạy một tập lệnh xác định để tạo ra bản đồ này.
Kiểm kê khoảng trống và người đánh giá hoài nghi
Ngôn ngữ mới có các yêu cầu khác với ngôn ngữ cũ phải được đáp ứng. Đối với Zig sang Rust, sự khác biệt là quản lý bộ nhớ thủ công (C và C++ hoạt động theo cùng một cách). Ví dụ:
1// Zig23fn readConfig(allocator: std.mem.Allocator) ![]u8 {4 const buf = try allocator.alloc(u8, 1024);5 // ...fill buf...6 return buf; // caller must free this — but only the comment says so7}89// A caller that forgets 'defer allocator.free(buf)' still compiles — the leak only surfaces at runtime.
1fn read_config() -> Vec<u8> {2 let buf = vec![0u8; 1024];3 // ...fill buf...4 buf // ownership moves to the caller; memory is freed automatically5}67// Use it after it's moved? Free it twice? Neither compiles.8// Forget to free it? There's no free call to forget — drop is automatic.
Đối với Python sang TypeScript, khoảng trống là các giao diện và hợp đồng. Python không yêu cầu một hợp đồng khai báo hình dạng của đối tượng mà nó sẽ chấp nhận hoặc trả về, nhưng TypeScript thì có.
Cả Jarred và Mike đều tạo các tệp kiểm kê khoảng trống ghi lại kiến thức tiềm ẩn này. Jarred đã kiểm kê các khoảng trống này ngay từ đầu, đó là những gì chúng tôi làm ở đây, trong khi Mike chọn dịch trước và sau đó tạo kiểm kê khoảng trống bằng cách kiểm toán sau. Bạn có thể cần phải làm cả hai.
Hãy xem mẫu prompt Claude Code này để tạo một tệp kiểm kê khoảng trống.
Bước 2 — Kiểm tra sức chịu đựng của các quy tắc

Trong bước này, Jarred đã sử dụng một agent để dịch ba tệp bằng cách sử dụng sổ tay quy tắc, một agent để dịch ba tệp "như một kỹ sư Rust cao cấp," và một agent để sử dụng sự khác biệt nhằm tạo ra các quy tắc dịch mới. Ở giai đoạn này, anh ấy đã phát hiện ra hai vấn đề nghiêm trọng có thể tạo ra vô số vấn đề nếu lan rộng ra tất cả 1.448 tệp.
Loại kiểm tra sức chịu đựng này chỉ hoạt động đối với các quá trình di chuyển bảo toàn cấu trúc, nơi hai bản dịch của cùng một tệp có thể so sánh từng dòng. Nếu sổ tay quy tắc của bạn là một thiết kế lại — giống như của Mike — thì bài kiểm tra tương đương là tấn công trực tiếp vào tài liệu thiết kế với các người đánh giá đối nghịch, sau đó xác thực nó bằng một lần chạy toàn bộ dùng một lần.
Bất kể thế nào, hãy loại bỏ bất kỳ tệp nào đã được dịch. Mục tiêu là tinh chỉnh các quy tắc, chứ không phải đạt được tiến bộ gia tăng.
Bước 3 — Dịch mọi thứ

Đối với các bước còn lại, bạn chạy cùng một kiến trúc vòng lặp đa agent: thực hiện, đánh giá và sửa chữa.
Bạn có thể chuyển công việc của người thực hiện sang các mô hình nhỏ hơn và giữ người đánh giá trên các mô hình lớn hơn. Ví dụ, Mike đã sử dụng Claude Sonnet khi anh ấy triển khai 12 sub-agent cho quá trình di chuyển chính.
Hàng đợi công việc nên mang tính cơ học. Một tập lệnh hàng loạt quyết định những gì đã hoàn thành bằng cách kiểm tra xem tệp đã dịch có tồn tại trên đĩa hay không, sau đó cắt các tệp đang chờ thành các đợt cho các agent thực hiện. Bởi vì hàng đợi được xây dựng lại từ đĩa mỗi lần, quá trình di chuyển có thể tiếp tục theo thiết kế.
Bất cứ điều gì mà người dịch không thể thực hiện một cách tự tin đều được gắn cờ bằng "// TODO(port): <reason>" để được xử lý ở bước 4.
Hai người đánh giá đối nghịch đánh giá công việc của người thực hiện bằng cách sử dụng các ngữ cảnh riêng biệt và sự bất đồng giữa những người đánh giá sẽ được chuyển đến một agent thứ ba. Khi một người đánh giá liên tục bắt gặp cùng một lỗi trên nhiều tệp, bản sửa lỗi không phải là trên từng tệp. Bạn thêm một câu vào sổ tay quy tắc và tạo lại đợt bị ảnh hưởng. Sổ tay quy tắc tiếp tục phát triển trong suốt bước này; mã không bao giờ được vá thủ công chống lại nó.
Một quyết định thiết kế quan trọng cần lưu ý trong bước này là vị trí của trình biên dịch. Mike đã chạy trình biên dịch TypeScript trong mọi vòng lặp, vì nó kiểm tra một đơn vị trong vài giây. Jarred đã cấm trình biên dịch khỏi vòng lặp hoàn toàn và hoãn nó sang bước tiếp theo, vì cargo mất vài phút.
Các bước 4, 5, 6 — Biên dịch, chạy và so khớp hành vi

Ba bước này chia sẻ cùng một kiến trúc vòng lặp và cần ngày càng ít phán đoán của con người, vì vậy chúng tôi đề cập chúng cùng nhau.
Jarred đã thực hiện điều này với một tập lệnh điều phối gọi trình biên dịch một lần trên toàn bộ không gian làm việc. Các "agent sửa lỗi" sau đó chạy qua danh sách lỗi song song với đánh giá đối nghịch. Bản dựng chạy lại, lặp đi lặp lại.
Xem xét danh sách lỗi rất hữu ích để phát hiện các vấn đề hệ thống có thể yêu cầu điều chỉnh. Ví dụ, Jarred gặp phải hàng nghìn lỗi mô-đun Rust xuất hiện sau khi sửa các vòng lặp phụ thuộc mà quá trình biên dịch lười biếng của Zig đã dung thứ. Anh ấy đã sửa vòng lặp bằng cách mã hóa logic để phân loại phụ thuộc nào cần xóa, di chuyển hoặc cấu trúc lại ranh giới.
Bước 5 cũng có một nguồn sự thật cơ học tương tự như danh sách lỗi trình biên dịch: sự cố từ kiểm tra khói. Một lần nữa, cách sửa vòng lặp là nhóm các vấn đề vào các danh mục, trong trường hợp này là nhóm các nguyên nhân theo nguyên nhân gốc rễ được xem xét bởi các sub-agent đối nghịch.
Bước 6 và là kết thúc câu chuyện của chúng tôi là so sánh hành vi của các chương trình trên hai mã nguồn.
Các tệp của chúng tôi hiện đã được dịch, biên dịch và kiểm tra khói.
Bây giờ là lúc phân chia chúng và chạy bộ kiểm thử (từ giai đoạn điều kiện tiên quyết) trên chúng. Giải quyết các lỗi bằng "các agent sửa lỗi" xem xét các kiểm thử thất bại trên cả hai mã nguồn. Các người đánh giá đối nghịch kiểm tra các bản sửa lỗi của họ.
Giai đoạn tiếp theo trong vòng lặp này là một daemon xây dựng, đây là quy trình duy nhất được phép xây dựng lại tệp nhị phân. Các agent sửa lỗi viết các bản vá; daemon nhóm chúng, xây dựng lại một lần, chạy lại các kiểm thử bị ảnh hưởng và đưa kết quả trở lại. Điều này tuần tự hóa hoạt động đắt nhất thay vì để nhiều agent kích hoạt nó một cách độc lập.
Cách tiếp cận của Mike rất quan trọng ở đây, bởi vì nhiều nhà phát triển sẽ không có một bộ kiểm thử đầy đủ hoặc đã được port. Mike đã yêu cầu Claude tạo một tập lệnh nhỏ để chạy 7 kịch bản thực tế trên cả bản port mới và mã nguồn Python gốc, và so sánh kết quả. Mỗi kịch bản thất bại có agent sửa lỗi riêng, và vòng lặp chạy cho đến khi tất cả bảy kịch bản đều vượt qua.
Sau đó, anh ấy đã tiến thêm một bước nữa. Claude đã thiết kế bộ kiểm thử từ đầu đến cuối của riêng nó và chạy nó một cách tự động qua đêm, sửa những gì bị hỏng và chạy lại bốn đêm liên tiếp. Kết quả là, nó đã phát hiện ra những vết cắt nhỏ mà không có danh sách kịch bản nào có thể dự đoán được.
Bài học là việc thiếu một bộ kiểm thử không ngăn chặn được bước này. Nếu bạn không thể kế thừa một trọng tài, hãy yêu cầu Claude xây dựng một trọng tài. Mã nguồn gốc của bạn là sự thật cơ bản trong mọi trường hợp.
Các phương pháp hay nhất cho việc di chuyển mã
Mỗi lần chạy đều dạy cho chúng tôi điều gì đó mà lần trước không có. Nhưng một vài phương pháp đã được giữ vững trong mọi dự án:
- Đừng làm theo hướng dẫn này một cách mù quáng. Mỗi lần di chuyển đều khác nhau. Hãy coi đây là một điểm khởi đầu và lên kế hoạch di chuyển cụ thể của bạn với Claude trước khi cam kết thực hiện.
- Đừng tập trung vào các lỗi riêng lẻ. Các lỗi riêng lẻ là công việc của vòng lặp. Sự chú ý của bạn thuộc về các mẫu.
- Làm cho việc đánh giá mang tính đối nghịch và xác minh mang tính cơ học. Hãy để các tập lệnh — trình biên dịch, sự khác biệt, bộ kiểm thử — làm trọng tài.
- Đừng sử dụng mô hình lớn nhất cho mọi thứ. Các mô hình nhỏ hơn xử lý tốt việc triển khai khối lượng lớn; hãy dành mô hình lớn nhất của bạn cho những người đánh giá và cho bất cứ thứ gì viết các quy tắc mà các agent khác sẽ tuân theo.
- Dồn giờ làm việc của con người vào giai đoạn đầu. Sổ tay quy tắc và kiểm tra sức chịu đựng là tốn thời gian nhất. Mọi thứ sau đó chủ yếu là các hàng đợi giảm dần.
Đánh giá kết quả vòng lặp, không phải mã
Quá trình di chuyển Bun của Jarred hiện đã được đưa vào sản xuất, mặc dù mọi cuộc di chuyển đều có sự đánh đổi. Ví dụ, khoảng 4% mã Rust nằm trong các khối "unsafe", chủ yếu là các thao tác con trỏ một dòng tại ranh giới C/C++.
Nhưng mã nguồn mới tốt hơn một cách rõ rệt. Mọi rò rỉ bộ nhớ mà công cụ của nhóm có thể phát hiện đã được sửa: một điểm chuẩn của 2.000 lần xây dựng lặp lại đã giảm từ 6.745 MB bộ nhớ xuống còn 609. Tệp nhị phân nhỏ hơn 19% trên Linux và Windows. Và tối ưu hóa đa ngôn ngữ đã làm cho nó nhanh hơn 2–5% trên các tác vụ phục vụ HTTP và khối lượng công việc thực tế như bản dựng next và tsc.
Hãy chọn mã nguồn mà bạn đã chấp nhận và hỏi Claude xem quy trình di chuyển cho nó trông như thế nào.





