Một AI agent có thể đọc hết 50.000 dòng code trong vài phút, nhưng chỉ một prompt hớ hênh cũng có thể khiến nó "sửa luôn" một bug mà hệ thống production đang âm thầm dựa vào suốt 8 năm qua.
Dùng AI để hiểu một codebase xa lạ
Khi tiếp quản một legacy codebase (codebase kế thừa, thường thiếu tài liệu và người viết ra đã nghỉ việc), việc đầu tiên không phải là code mà là đọc hiểu. Đây chính là nơi AI agent tạo ra giá trị lớn nhất nhưng lại thường bị đánh giá thấp nhất. Thay vì bạn phải tự lần theo từng file, từng lời gọi hàm để dựng lại bức tranh kiến trúc trong đầu, bạn có thể giao cho agent việc "khảo cổ" (archaeology) codebase: yêu cầu nó vẽ ranh giới module, tóm tắt luồng dữ liệu, và giải thích tại sao một đoạn logic kỳ quặc lại tồn tại.
Kỹ thuật hiệu quả nhất là chia nhỏ việc khám phá thành các câu hỏi có phạm vi hẹp thay vì hỏi chung chung "giải thích codebase này giúp tôi". Một agent với context window (cửa sổ ngữ cảnh) giới hạn sẽ trả lời hời hợt nếu bạn hỏi quá rộng. Hãy bắt đầu từ một entry point cụ thể — một controller, một cron job, một message handler — rồi yêu cầu agent trace (lần theo) toàn bộ vòng đời request từ đó. Ví dụ prompt: "Explain the full request lifecycle of OrderController.checkout() in this file — trace every function it calls, every database table it touches, and every external service it invokes. Present it as a numbered sequence." Với các hàm được gọi ở nhiều nơi, prompt hữu ích khác: "List all callers of applyDiscount() in this repo, and for each caller describe what side effects depend on its return value (state mutation, logging, external API calls)."
Một ứng dụng nâng cao hơn là dùng AI để trả lời câu hỏi "tại sao" — lý do nghiệp vụ (business logic) đằng sau một đoạn code khó hiểu. Agent không thể đọc được ý định trong đầu người viết code cũ, nhưng nó có thể đối chiếu comment, tên biến, commit message, ticket liên quan (nếu bạn cung cấp) để đưa ra giả thuyết hợp lý. Prompt gợi ý: "Based on this function's code, comments, and git blame history, what business rule is it likely enforcing? List assumptions you're making so I can verify them with the team." Đừng tin tuyệt đối câu trả lời này — coi nó là điểm khởi đầu để bạn xác minh với đồng nghiệp hoặc tài liệu nghiệp vụ, không phải sự thật tuyệt đối.
Cuối cùng, hãy tận dụng agent để sinh tài liệu onboarding từ chính code — một README kiến trúc, một sơ đồ module dependency, hay một bảng tổng hợp "các class nguy hiểm cần cẩn thận khi sửa". Việc này biến kiến thức ngầm (tacit knowledge) mà agent vừa "học" được thành tài sản chia sẻ cho cả team, thay vì mất đi khi phiên chat kết thúc.
Mẹo: Luôn yêu cầu agent trích dẫn số dòng và tên file cụ thể khi giải thích code — nếu nó không trích dẫn được, khả năng cao nó đang suy đoán chứ không thực sự đọc.
Các pattern refactor an toàn cùng AI
Refactor an toàn trên legacy codebase tuân theo một nguyên tắc bất biến: mỗi bước thay đổi chỉ nên chạm vào một hành vi (behavior) duy nhất, và phải có thể verify ngay sau đó. AI agent làm cho việc vi phạm nguyên tắc này dễ dàng hơn bao giờ hết — nó có thể tự tin viết lại cả một module trong một lần trả lời, nhưng "tự tin" không đồng nghĩa "đúng". Vai trò của bạn là ép agent làm việc theo từng bước nhỏ, có thể kiểm chứng, thay vì để nó làm big-bang rewrite (viết lại toàn bộ trong một lần).
Phân biệt rõ hai loại refactor giúp bạn quyết định mức độ giám sát cần thiết. Refactor cơ học (mechanical refactor) — rename biến/hàm, extract function/method (tách một đoạn logic thành hàm riêng), inline variable (gộp biến vào biểu thức sử dụng nó), di chuyển code giữa các file — là những phép biến đổi có thể tự động hóa và verify bằng công cụ, rủi ro thấp, rất phù hợp để giao toàn quyền cho AI. Refactor ngữ nghĩa (semantic refactor) — đổi thuật toán, gộp hai luồng xử lý tưởng như giống nhau, thay đổi kiểu dữ liệu, đổi thứ tự side effect — tiềm ẩn rủi ro thay đổi hành vi thực sự, cần review kỹ từng dòng diff và không nên để agent tự quyết định phạm vi.
Quy trình thực tế: (1) yêu cầu agent thực hiện đúng một refactor cụ thể, phạm vi hẹp; (2) chạy toàn bộ test suite liên quan; (3) đọc diff — không phải đọc lướt, mà đọc từng thay đổi và tự hỏi "điều này có làm thay đổi output không?"; (4) commit nếu ổn, rồi mới chuyển sang bước tiếp theo. Prompt mẫu cho một bước refactor cơ học: "Extract the pricing calculation logic (lines 120-165) in OrderManager.java into a private method calculateFinalPrice(). Do not change any logic, variable values, or execution order — this is a pure extraction, verify by re-reading the extracted method against the original block line by line."
Việc chia nhỏ commit theo từng refactor giúp bạn có một chuỗi lịch sử dễ revert nếu phát hiện vấn đề muộn, và giúp reviewer (kể cả chính bạn ở tương lai) không phải đối chiếu một diff khổng lồ lẫn lộn giữa refactor cơ học và thay đổi logic thật sự.
Mẹo: Sau mỗi bước refactor, chạy diff giữa output test trước và sau (không chỉ xem pass/fail) — một số bug tinh vi vẫn "pass" test cũ vì test cũ vốn không cover đúng case đó.
Ra lệnh cho agent giữ nguyên hành vi hiện có
Đây là điểm dễ gây thảm họa nhất khi dùng AI agent với legacy code: agent có xu hướng "sửa" luôn những gì nó cho là bug trong lúc bạn chỉ yêu cầu refactor thuần túy. Một class 10 năm tuổi có thể chứa hàng chục quirk (hành vi kỳ lạ nhưng cố ý, hoặc bug đã được các hệ thống downstream "thích nghi" theo) — và agent, với bản năng muốn viết "code sạch, đúng chuẩn", rất dễ âm thầm loại bỏ chúng.
Giải pháp bắt buộc là ràng buộc rõ ràng ngay trong prompt: nói thẳng "không được thay đổi hành vi", và quan trọng hơn — yêu cầu agent viết characterization test (test đặc tả hành vi hiện tại, bao gồm cả bug/quirk) TRƯỚC khi chạm vào code refactor. Characterization test khác với unit test thông thường ở chỗ nó không kiểm tra "đúng theo spec", mà kiểm tra "đúng như hệ thống đang chạy thật, bất kể đúng sai". Prompt mẫu: "Write characterization tests for LegacyPricingEngine.calculate() that capture its CURRENT output for these 15 input scenarios, including any inconsistent rounding behavior you observe. Do not fix or normalize the behavior — the test must fail if the output changes in ANY way, even if the current output looks like a bug."
Sau khi có bộ characterization test làm lưới an toàn, mọi refactor tiếp theo phải giữ cho bộ test này pass 100%. Nếu agent phát hiện ra thứ trông giống bug thật sự trong lúc refactor, quy tắc là: dừng lại, báo cáo phát hiện, KHÔNG tự sửa. Prompt ràng buộc rõ: "If you notice what looks like a bug during this refactor, do NOT fix it. Stop, describe the issue in a comment, and continue only with the pure refactor. Bug fixes will be a separate, explicit task with its own review."
Golden-master testing (còn gọi là snapshot testing) là kỹ thuật bổ trợ mạnh cho các hệ thống có output phức tạp (báo cáo, HTML, JSON response) — ghi lại output hiện tại làm "bản vàng", rồi so sánh tự động sau mỗi thay đổi. Kết hợp cả hai: unit-level characterization test cho logic nhỏ, golden-master cho luồng end-to-end, bạn có một lưới an toàn hai lớp đủ tin cậy để refactor mạnh dạn hơn.
Mẹo: Luôn tự chạy test suite bằng tay để xác nhận kết quả — đừng chỉ tin vào câu agent báo "tests pass", vì đôi khi agent chỉ đọc code test mà không thực sự execute nó.
Áp dụng Strangler Fig Pattern với sự hỗ trợ của AI
Strangler fig pattern (tạm dịch: mẫu hình "cây đa bóp nghẹt" — lấy cảm hứng từ loài cây nhiệt đới mọc bao quanh và dần thay thế cây chủ) là chiến lược hiện đại hóa an toàn nhất cho các hệ thống lớn không thể refactor "một phát ăn ngay". Thay vì viết lại toàn bộ rồi cắt chuyển một lần (rủi ro cực cao), bạn xây implementation mới chạy song song với implementation cũ, rồi dần dần chuyển traffic/lời gọi từ cũ sang mới, cho đến khi implementation cũ không còn ai gọi tới và có thể xóa an toàn.
AI agent rất phù hợp để scaffold (dựng khung) phần implementation mới, vì đây thường là công việc lặp lại theo pattern đã biết — bạn có logic cũ làm tham chiếu, agent chỉ cần viết lại theo kiến trúc/coding convention mới trong khi giữ đúng hành vi. Bước quan trọng nhất là lớp facade/adapter đứng giữa hai implementation — nơi quyết định route một lời gọi tới code cũ hay code mới, thường điều khiển bằng feature flag. Prompt mẫu để agent sinh lớp facade: "Create a PricingFacade class that implements the same interface as LegacyPricingEngine. It should check a feature flag use_new_pricing_engine (per customer ID) — if true, delegate to NewPricingEngine, otherwise delegate to LegacyPricingEngine. Log every call with which implementation was used and the output, so we can compare them in production without affecting behavior."
Giai đoạn "so sánh song song" (shadow mode) là chỗ AI hỗ trợ tốt nhất về mặt phân tích: chạy cả hai implementation cho cùng input thật, ghi log kết quả cả hai, rồi để agent phân tích log tìm ra các trường hợp lệch nhau. Prompt: "Analyze these 500 log entries where both the legacy and new pricing engine ran on the same input. List every case where outputs differ, group them by pattern, and hypothesize the root cause for each group." Chỉ khi tỷ lệ lệch giảm về gần 0 qua đủ lâu (theo ngưỡng team tự đặt, tùy mức độ rủi ro nghiệp vụ), bạn mới tăng dần % traffic route sang implementation mới, rồi cuối cùng xóa code cũ.
Đừng vội xóa code cũ ngay khi implementation mới "có vẻ ổn" — giữ nó lại (đã tắt qua flag) thêm một chu kỳ nghiệp vụ đầy đủ (ví dụ hết một tháng chốt sổ, một quý báo cáo) để chắc chắn không có edge case theo mùa/định kỳ nào bị bỏ sót.
Mẹo: Luôn để feature flag strangler fig có thể rollback tức thời (không cần deploy lại) — đây là lưới an toàn cuối cùng nếu implementation mới gãy ở production.
Các kiểu thất bại thường gặp
Hiểu rõ các kiểu thất bại phổ biến giúp bạn nhận diện sớm khi agent đang "lạc đường" thay vì phát hiện ra ở production. Thất bại nguy hiểm nhất là agent tự bịa ra hành vi không hề tồn tại trong code gốc — nó "đoán" logic hợp lý dựa trên pattern quen thuộc thay vì đọc kỹ code thực tế, đặc biệt dễ xảy ra khi file quá dài và phần quan trọng nằm ngoài phần agent đang "chú ý" nhiều nhất.
Một dạng khác tinh vi hơn: âm thầm bỏ sót xử lý edge case. Legacy code thường có hàng loạt if-else xử lý những trường hợp hiếm gặp (dữ liệu null, đơn vị tiền tệ đặc biệt, khách hàng legacy dùng format cũ) mà không hề có comment giải thích tại sao. Khi refactor, agent có xu hướng "dọn dẹp" các nhánh trông thừa thãi này — vì thiếu ngữ cảnh nghiệp vụ để biết chúng quan trọng.
Over-refactoring (refactor vượt phạm vi yêu cầu) cũng là lỗi thường gặp: bạn yêu cầu extract một hàm, agent tiện tay đổi luôn tên biến ở 20 chỗ khác, format lại toàn bộ file, hoặc "tối ưu" một đoạn logic không liên quan. Điều này làm diff phình to, khó review, và tăng rủi ro không cần thiết. Tương tự, agent dễ phá vỡ các hợp đồng ngầm (implicit contract) — ví dụ một hàm phụ thuộc vào side effect (tác dụng phụ) như ghi log theo thứ tự cụ thể, biến global state, hay timing giữa các lời gọi — những thứ không nằm trong signature hay test, nên agent không "nhìn thấy" được.
Cuối cùng, hai lỗi mang tính hệ thống cần đặc biệt cảnh giác: (1) agent bị mất ngữ cảnh (lose context) khi làm việc với file rất lớn, dẫn đến các thay đổi ở đầu và cuối file không nhất quán với nhau; và (2) false confidence (tự tin giả) — agent tuyên bố "tests pass" hoặc "behavior preserved" mà không thực sự chạy lệnh kiểm thử, chỉ suy luận dựa trên việc đọc code. Luôn đòi hỏi bằng chứng thực thi (execution log, output thực tế) chứ không chỉ lời khẳng định.
Mẹo: Với file trên vài trăm dòng, hãy chủ động chia nhỏ phạm vi cho agent (từng class/method một) thay vì ném cả file và hy vọng nó nhớ hết.
Hands-On: Refactor một God Class 500 dòng
Bài thực hành này mô phỏng tình huống rất thường gặp: một class OrderManager khoảng 500 dòng, gánh vác cùng lúc validation (kiểm tra hợp lệ đơn hàng), persistence (lưu database), notification (gửi email/SMS xác nhận), và pricing (tính giá, áp dụng khuyến mãi) — một god class (class ôm đồm quá nhiều trách nhiệm) kinh điển vi phạm Single Responsibility Principle. Mục tiêu: tách thành các class chuyên trách, không làm gãy bất kỳ hành vi hiện có nào, kể cả các quirk khó chịu.
Bước 1 — Sinh characterization test bao phủ toàn bộ class. Trước khi động vào bất kỳ dòng code nào, yêu cầu agent tạo lưới an toàn: "Read OrderManager.java fully. Write characterization tests covering every public method — placeOrder(), validateOrder(), calculatePrice(), sendConfirmation(), persistOrder(). For each method, generate at least 5 test cases covering typical input, edge cases (null, empty, negative values), and any branch you find in the code. Capture the CURRENT output exactly, do not assume what the 'correct' output should be." Chạy bộ test này, xác nhận coverage đủ cao (ít nhất bao phủ hết các nhánh if/else chính) trước khi qua bước 2.
Bước 2 — Yêu cầu agent xác định ranh giới trách nhiệm (seams). Seam ở đây là điểm có thể tách code mà không cần thay đổi logic bên trong. Prompt: "Analyze OrderManager.java and identify distinct responsibilities mixed into this class. For each responsibility, list which methods/fields belong to it, and note any methods that depend on state from another responsibility (these are the risky seams to handle carefully). Do not write any code yet — just produce the analysis." Đọc kỹ output này cùng agent — đây là lúc bạn, người hiểu nghiệp vụ, xác nhận lại các ranh giới có hợp lý không trước khi bắt tay refactor.
Bước 3 — Tách từng trách nhiệm một, theo thứ tự rủi ro thấp đến cao. Bắt đầu từ phần ít phụ thuộc chéo nhất — thường là notification. Prompt: "Extract all notification-related logic (methods: sendConfirmation(), sendCancellationEmail(), and related private helpers) from OrderManager into a new class OrderNotifier. OrderManager should hold a reference to OrderNotifier and delegate calls to it. Preserve exact method signatures called from outside and exact behavior — this is a pure extraction, no logic changes." Sau đó lần lượt làm tương tự với persistence (OrderPersistence) và pricing (OrderPricingCalculator), để lại OrderManager chỉ còn vai trò điều phối (orchestration) và validation cốt lõi.
Bước 4 — Chạy lại toàn bộ characterization test sau MỖI lần extract. Đây không phải bước tùy chọn. Sau mỗi lần tách một trách nhiệm, chạy full suite, xác nhận 100% pass trước khi tiếp tục sang trách nhiệm kế tiếp. Nếu có test fail, không cố "vá" test — quay lại xem extraction có thực sự pure (không đổi logic) hay không. Prompt hữu ích khi có test fail: "This characterization test now fails after the extraction. Compare the extracted method to the original code line by line and tell me exactly what changed in behavior, execution order, or state access."
Bước 5 — Review kiến trúc cuối cùng. Sau khi tách xong 3-4 class con, yêu cầu agent tự đánh giá lại: "Review the final architecture: OrderManager, OrderNotifier, OrderPersistence, OrderPricingCalculator. Check for any remaining hidden coupling (shared mutable state, hidden call-order dependencies), and confirm each class now has a single clear responsibility. Summarize what changed structurally, and confirm no test in the characterization suite was modified or skipped." Bước này vừa là kiểm tra chất lượng kỹ thuật, vừa là tài liệu hóa lại quyết định kiến trúc cho người review sau này.
Mẹo: Giữ nguyên bộ characterization test này lại sau khi refactor xong — nó trở thành lưới an toàn thường trực (regression test) cho mọi thay đổi tương lai lên các class mới tách ra.
Những điểm chính
- Dùng AI agent để "khảo cổ" legacy codebase: trace request lifecycle, liệt kê caller và side effect, truy tìm lý do nghiệp vụ đằng sau code khó hiểu — nhưng luôn xác minh giả thuyết của agent với thực tế.
- Refactor an toàn = từng bước nhỏ, một hành vi một lần, chạy test và đọc diff sau mỗi bước; phân biệt rõ refactor cơ học (an toàn, giao cho AI) và refactor ngữ nghĩa (rủi ro, cần review kỹ).
- Bắt buộc viết characterization test trước khi refactor, ràng buộc rõ "không tự sửa bug" trong prompt, và tự chạy test để xác nhận thay vì tin lời agent báo "tests pass".
- Strangler fig pattern: xây implementation mới song song, dùng facade + feature flag để route dần traffic, dùng AI phân tích log shadow mode trước khi xóa code cũ.
- Cảnh giác các failure mode: bịa hành vi, bỏ sót edge case, over-refactor, phá vỡ implicit contract, mất context ở file lớn, false confidence.
- Với god class: characterization test toàn diện → xác định seam → extract từng trách nhiệm theo thứ tự rủi ro thấp đến cao → test lại sau mỗi bước → review kiến trúc cuối cùng.