AI chuyển mã giữa các ngôn ngữ khá nhanh và thường đúng cú pháp, nhưng chỗ hay sai không nằm ở cú pháp mà ở những khác biệt ngầm về hành vi: làm tròn số, so sánh giữa chuỗi và số, giá trị nào được coi là "sai", chia số nguyên, tháng bắt đầu từ 0 hay 1. Vì vậy cách giao việc đúng là chốt hành vi của mã cũ bằng ví dụ trước, chuyển từng phần nhỏ, rồi chạy cùng một bộ ví dụ trên cả hai bản.
Bài này đi qua câu lệnh mẫu cho việc chuyển mã, bảng các khác biệt hay làm hỏng kết quả giữa PHP, Python và JavaScript, những lưu ý riêng khi chuyển JavaScript sang TypeScript, và quy trình kiểm. Các đoạn mã trong bài là ví dụ ngắn để minh hoạ hành vi; kiểm lại trên đúng phiên bản bạn đang chạy.
Vì sao "dịch từng dòng" là cách nguy hiểm nhất
Khi được yêu cầu chuyển mã, trợ lý AI có xu hướng dịch từng dòng: vòng lặp thành vòng lặp, hàm thành hàm, biến giữ nguyên tên. Kết quả đọc lên rất giống bản gốc nên người kiểm dễ yên tâm. Nhưng hai dòng trông tương đương ở hai ngôn ngữ có thể cho kết quả khác nhau với cùng đầu vào.
Ví dụ quen thuộc nhất là hàm làm tròn. Trong PHP, round(2.5) cho 3 vì PHP làm tròn nửa ra xa số không. Trong Python 3, round(2.5) cho 2 vì Python làm tròn nửa về số chẵn gần nhất. Một hệ thống tính tiền chuyển từ PHP sang Python mà giữ nguyên lời gọi round sẽ lệch một đồng ở một số hoá đơn, đủ để đối soát cuối tháng không khớp mà không ai biết vì sao.
Loại khác biệt này không gây lỗi khi chạy, không sinh cảnh báo, và chỉ lộ ra với những đầu vào cụ thể. Trợ lý đôi khi tự nhận ra và chú thích, đôi khi không. Bạn không thể trông vào việc nó luôn nhớ; bạn cần một bộ ví dụ để so.
Quy trình chuyển mã an toàn
Trình tự dưới áp dụng cho cả việc chuyển một hàm lẫn một mô-đun nhỏ:
- Chọn phạm vi nhỏ: một hàm hoặc một tệp có ít phụ thuộc. Không chuyển cả dự án trong một yêu cầu.
- Ghi lại hành vi hiện tại: chạy mã cũ với một bộ đầu vào đa dạng, có ca biên, lưu kết quả ra tệp. Có thể nhờ AI gợi ý danh sách đầu vào, nhưng kết quả phải lấy từ việc chạy thật mã cũ.
- Giao việc chuyển, kèm bộ đầu vào và kết quả đó, yêu cầu bản mới phải cho đúng các kết quả này.
- Yêu cầu trợ lý liệt kê mọi chỗ mà hành vi của hai ngôn ngữ có thể khác nhau trong đoạn mã cụ thể này.
- Chạy bản mới với cùng bộ đầu vào, so sánh từng dòng kết quả với bản cũ.
- Chỉ sau khi khớp hoàn toàn mới yêu cầu viết lại cho đúng phong cách của ngôn ngữ mới.
Bước cuối tách riêng có chủ đích. Bản dịch sát giúp so sánh dễ; khi đã chắc hành vi giống nhau, việc làm cho mã "đúng kiểu Python" hay "đúng kiểu TypeScript" trở thành một lần tái cấu trúc bình thường, có bộ ví dụ giữ lưng.
Câu lệnh mẫu cho việc chuyển mã
Mẫu dưới đây dùng cho một hàm, bài toán giả định. Điền phần trong ngoặc vuông:
- "Chuyển hàm sau từ [PHP 7.4] sang [Python 3.11]. Mục tiêu là giữ NGUYÊN hành vi, kể cả những hành vi có vẻ lạ."
- "Mã gốc: [dán hàm]."
- "Bảng đầu vào và kết quả lấy từ việc chạy mã gốc: [dán bảng]. Bản mới phải cho đúng các kết quả này."
- "Trước khi viết, liệt kê các điểm hành vi có thể khác nhau giữa hai ngôn ngữ trong đúng đoạn mã này: làm tròn, chia số, so sánh, giá trị rỗng, xử lý chuỗi có dấu, ngày giờ."
- "Viết bản dịch sát, chưa tối ưu. Mỗi chỗ phải xử lý đặc biệt để giữ hành vi thì thêm chú thích giải thích."
- "Kèm một tệp kiểm thử chạy toàn bộ bảng đầu vào trên."
Câu "kể cả những hành vi có vẻ lạ" quan trọng. Trợ lý hay tự sửa những gì nó cho là lỗi của mã gốc. Đôi khi đó đúng là lỗi, nhưng hệ thống khác có thể đang dựa vào chính hành vi đó. Sửa lỗi là một việc riêng, có quyết định riêng, không trộn vào lúc chuyển ngôn ngữ.
Những khác biệt ngầm hay làm mã dịch chạy sai
Bảng tóm tắt một số khác biệt phổ biến giữa PHP, Python và JavaScript. Hành vi có thể thay đổi giữa các phiên bản lớn, nên chạy thử trên đúng phiên bản của bạn.
| Chủ đề | PHP | Python 3 | JavaScript |
|---|---|---|---|
| Làm tròn nửa | round(2.5) ra 3, round(-2.5) ra -3 | round(2.5) ra 2, làm tròn về số chẵn | Math.round(2.5) ra 3, Math.round(-2.5) ra -2 |
| Phép chia | 7 / 2 ra 3.5, intdiv(7, 2) ra 3 | 7 / 2 ra 3.5, 7 // 2 ra 3 | 7 / 2 ra 3.5, không có kiểu số nguyên riêng |
| Chuỗi "0" khi kiểm điều kiện | Được coi là sai | Được coi là đúng vì chuỗi khác rỗng | Được coi là đúng |
| Mảng rỗng khi kiểm điều kiện | Sai | Danh sách rỗng là sai | Mảng rỗng vẫn là đúng |
| So sánh lỏng chuỗi với số | Quy tắc đổi giữa PHP 7 và PHP 8 | Không tự đổi kiểu, so sánh bằng ra sai | Toán tử == tự đổi kiểu, === thì không |
| Tháng trong ngày giờ | Tháng từ 1 đến 12 | Tháng từ 1 đến 12 | Đối tượng Date đánh số tháng từ 0 |
Mảng của PHP là một trường hợp đặc biệt khác: một kiểu dữ liệu đảm nhiệm cả danh sách lẫn từ điển có thứ tự. Khi chuyển sang Python, trợ lý phải quyết định mỗi mảng thành list hay dict, và quyết định sai thì mã vẫn chạy cho tới khi gặp một khoá không liên tục. Hãy yêu cầu nó nói rõ mỗi mảng được chuyển thành kiểu gì và vì sao.
Chuyển JavaScript sang TypeScript: những lối tắt cần chặn
Kiểu any rải khắp nơi. Cách nhanh nhất để mã TypeScript biên dịch được là gắn kiểu any cho mọi thứ khó. Mã khi đó hết báo lỗi nhưng cũng mất gần hết lợi ích của việc chuyển. Ghi rõ trong yêu cầu: không dùng any; chỗ nào chưa xác định được kiểu thì dùng unknown và kiểm tra trước khi dùng, kèm chú thích.
Ép kiểu bằng as để bịt lỗi. Khi trình biên dịch báo hai kiểu không khớp, trợ lý có thể thêm as để ép, thay vì sửa logic. Mỗi lần ép là một chỗ bạn nói với trình biên dịch "cứ coi như đúng kiểu" mà không có kiểm tra thật. Yêu cầu liệt kê mọi chỗ dùng as và lý do.
Bật chế độ kiểm tra nghiêm ngặt. Nếu dự án mới chuyển, nói rõ có bật chế độ strict trong tệp cấu hình hay không. Mã viết cho chế độ lỏng sẽ báo hàng loạt lỗi khi bật strict sau này; chuyển một lần cho đúng thì đỡ hơn.
Kiểu cho dữ liệu từ bên ngoài. Dữ liệu từ API hay từ form không tự đúng kiểu chỉ vì bạn khai báo kiểu cho nó. Yêu cầu trợ lý chỉ ra chỗ nào dữ liệu đi vào từ bên ngoài và đề xuất kiểm tra lúc chạy ở đó.
Chuyển phần phụ thuộc thư viện: đừng để AI bịa tương đương
Mã gốc gọi thư viện của hệ sinh thái cũ: một gói gửi thư của PHP, một thư viện xử lý ngày của JavaScript. Ngôn ngữ mới cần thư viện tương đương, và đây là chỗ trợ lý dễ bịa nhất. Nó có thể đề xuất một tên gói nghe rất hợp lý nhưng không tồn tại, hoặc tồn tại nhưng có hàm khác hẳn với những gì nó viết trong mã.
Cách an toàn là tách việc chọn thư viện ra khỏi việc chuyển mã. Hỏi trước: "Những thư viện nào trong mã gốc cần thay thế? Với mỗi cái, đề xuất cách làm bằng thư viện chuẩn của Python nếu được; nếu phải dùng gói ngoài, nêu tên gói và ghi rõ cần kiểm lại." Sau đó bạn tự tra từng tên gói trên kho gói chính thức của ngôn ngữ, xem gói có thật, còn được duy trì, và có hàm như trợ lý mô tả không.
Rủi ro không chỉ là mã chạy lỗi. Tên gói do AI bịa đôi khi trùng với gói do kẻ xấu đăng sẵn để chờ người cài nhầm. Phần này có bài riêng ở danh mục an toàn mã nguồn của trang; ở đây chỉ cần nhớ: không cài một gói chỉ vì trợ lý nhắc tới nó.
Chuyển kèm bộ kiểm thử, đừng chỉ chuyển mã
Nếu mã gốc đã có kiểm thử, chuyển kiểm thử trước rồi mới chuyển mã. Bộ kiểm thử dịch sang ngôn ngữ mới, chạy trên mã mới, là bằng chứng mạnh nhất rằng hành vi được giữ. Yêu cầu trợ lý dịch kiểm thử theo nguyên tắc ngược với dịch mã: không được sửa giá trị mong đợi, kể cả khi trông vô lý; chỗ nào thấy nghi ngờ thì ghi chú để bạn quyết.
Nếu mã gốc chưa có kiểm thử, bảng đầu vào và kết quả bạn ghi lại ở bước đầu chính là bộ kiểm thử tối thiểu. Giữ nó trong dự án mới. Sau này khi tái cấu trúc bản mới cho đúng phong cách, bộ đó tiếp tục bảo vệ hành vi mà hệ thống khác đang dựa vào.
Một lưu ý về khung kiểm thử: mỗi ngôn ngữ có khung phổ biến riêng, như PHPUnit cho PHP, pytest cho Python, Jest hoặc Vitest cho JavaScript. Nói rõ khung bạn muốn dùng ở dự án mới để trợ lý không tự chọn một khung mà đội chưa quen.
Khi nào không nên chuyển ngôn ngữ
Trước khi giao việc, đáng tự hỏi việc chuyển có thật sự cần không. Lý do hợp lý thường là: đội không còn ai biết ngôn ngữ cũ, máy chủ mới không hỗ trợ, hoặc một phần hệ thống cần thư viện chỉ có ở ngôn ngữ mới. Lý do yếu là "ngôn ngữ mới hiện đại hơn" hay "AI làm nhanh nên làm luôn".
Tốc độ của AI làm việc chuyển mã có vẻ rẻ, nhưng chi phí thật nằm ở kiểm tra, triển khai và vận hành song song trong thời gian chuyển. Một hệ thống chạy ổn nhiều năm có những hành vi không ai ghi lại, và bộ ví dụ của bạn khó phủ hết. Với hệ thống như vậy, cải tạo từng phần ngay trong ngôn ngữ cũ — chủ đề của danh mục đọc hiểu và cải tạo mã cũ trên trang — thường ít rủi ro hơn chuyển hẳn.
Nếu vẫn quyết định chuyển, làm song song: bản mới xử lý một phần nhỏ lưu lượng hoặc chạy chế độ đối chiếu, so kết quả với bản cũ một thời gian trước khi tắt bản cũ.
Câu hỏi thường gặp
AI có chuyển được cả dự án lớn trong một lần không?
Về mặt kỹ thuật, tác tử AI có thể sinh ra rất nhiều mã trong một lượt, nhưng kiểm được hết thì gần như không thể. Chuyển theo từng mô-đun nhỏ, mỗi mô-đun có bộ ví dụ riêng, tốn nhiều lượt hơn nhưng bạn biết chắc phần nào đã đúng.
Nên dịch sát hay viết lại cho đúng phong cách ngôn ngữ mới ngay từ đầu?
Dịch sát trước, viết lại sau. Dịch sát giúp so sánh với mã gốc dòng theo dòng khi có kết quả lệch. Khi bộ ví dụ đã khớp hoàn toàn, việc làm đẹp mã trở thành tái cấu trúc có kiểm thử giữ lưng, ít rủi ro hơn nhiều.
Làm sao biết bộ ví dụ đã đủ phủ hành vi của mã cũ?
Không bao giờ chắc hoàn toàn, nhưng có thể tăng độ phủ: lấy đầu vào từ log thật đã che dữ liệu cá nhân, thêm ca biên cho mọi phép so sánh và làm tròn, và yêu cầu trợ lý đọc mã gốc rồi đề xuất thêm đầu vào chạm vào từng nhánh điều kiện.
Chuyển mã có tác dụng với câu SQL không?
Có, khi đổi hệ quản trị cơ sở dữ liệu, ví dụ từ MySQL sang PostgreSQL. Khác biệt ngầm ở đây là cách so sánh chuỗi có phân biệt hoa thường, kiểu ngày giờ, hàm xử lý chuỗi và cách trích dẫn tên cột. Nguyên tắc vẫn vậy: chạy cùng câu trên dữ liệu thử ở cả hai bên và so kết quả.
Có nên nhờ AI chuyển mã từ một dự án mã nguồn mở sang dự án của mình không?
Mã chuyển ngôn ngữ vẫn có thể là sản phẩm phái sinh của mã gốc, nên giấy phép của dự án gốc vẫn có thể áp dụng. Đọc điều khoản giấy phép trước khi làm, và nếu dự án thương mại có quy định riêng, hỏi người phụ trách pháp lý của công ty.