0

TƯ DUY LÀM VIỆC VỚI CÔNG NGHỆ VÀ AI Bài 12: Bóc tách bài toán Legacy — Phương pháp tiếp cận và cải tổ hệ thống cũ mà không làm đứt gãy kinh doanh

1. Cạm bẫy "Đập đi xây lại từ đầu" (The Big Bang Rewrite Fallacy)

Đứng trước một hệ thống Monolith cũ kỹ, code rối như tơ vò, thiếu test và liên tục phát sinh lỗi, phản xạ tự nhiên của 90% lập trình viên là đề xuất với sếp: "Hệ thống này nát quá rồi, hãy cho bọn em 6 tháng để viết lại phiên bản v2.0 từ đầu bằng ngôn ngữ mới và kiến trúc chuẩn chỉnh hơn!"

Nhà sáng lập Joel Spolsky từng gọi đây là sai lầm chiến lược lớn nhất mà một công ty công nghệ có thể mắc phải, vì 3 lý do chí mạng sau:

  • Code cũ nhìn có vẻ "bẩn", nhưng nó đang nuôi sống cả công ty: Những đoạn if-else kỳ dị, những dòng xử lý ngoại lệ trông có vẻ vô lý trong hệ thống Legacy thực chất là kết tinh của hàng trăm lần vá lỗi thực chiến (Battle-tested Edge Cases) suốt nhiều năm qua. Khi đập đi viết lại từ con số không, bạn chắc chắn sẽ lặp lại toàn bộ những sai lầm mà thế hệ đi trước đã từng đổ máu để sửa.

  • Đuổi theo một mục tiêu di động (Moving Target): Trong 6 tháng đội ngũ của bạn đóng cửa xây hệ thống mới (v2), thị trường không đứng yên. Bộ phận kinh doanh vẫn buộc phải yêu cầu thêm tính năng mới vào hệ thống cũ (v1). Kết quả là v2 chưa kịp ra mắt đã trở nên lỗi thời về mặt nghiệp vụ so với v1, kéo dự án vào vòng lặp trễ hạn vô tận (1 năm, 2 năm rồi bị khai tử).

  • Sự ngạo mạn thế hệ: Chúng ta thường chê người viết code cũ là kém cỏi mà quên mất rằng họ đã phải viết trong những ràng buộc ngặt nghèo về thời gian, hạ tầng và yêu cầu nghiệp vụ liên tục đảo chiều của giai đoạn khởi nghiệp.

Nghệ thuật cải tổ hệ thống Legacy không phải là làm cách mạng đập phá, mà là thay động cơ máy bay ngay khi chiếc máy bay đó đang bay ở độ cao 10.000 mét mà hành khách trên khoang không hề hay biết.

2. Bốn chiến thuật "Phẫu thuật không chảy máu" cho hệ thống Legacy

Để vừa trả nợ kỹ thuật, vừa tách dần các module quá tải sang kiến trúc mới mà vẫn đảm bảo dòng tiền kinh doanh chảy đều mỗi giây, kỹ sư trưởng thành sử dụng bộ tứ chiến thuật chuyển đổi tuần tiến (Incremental Modernization):

Các Chiến Thuật Cải Tổ Hệ Thống Thực Chiến

1. Strangler Fig Pattern (Cây đa bóp nghẹt)

  • Cơ chế vận hành thực chiến: Đặt một lớp điều hướng (API Gateway / Proxy Router) đứng trước hệ thống cũ. Khi có tính năng mới hoặc nâng cấp module nào (ví dụ: Module Thanh toán), hãy xây dựng nó ở service mới và cấu hình Router bẻ luồng truy cập của đúng endpoint đó sang nơi mới. Phần còn lại vẫn đi vào Monolith cũ.
  • Bài toán áp dụng lý tưởng: Tách dần một khối Monolith khổng lồ (PHP/Java cũ) sang các dịch vụ hiệu năng cao (Golang/NestJS) theo từng miền nghiệp vụ (Domain) qua nhiều tháng.
  • Chốt chặn an toàn (Safety Net): Nếu service mới gặp sự cố trên Production, chỉ mất 5 giây đổi cấu hình trên Gateway/Feature Flag để bẻ 100% luồng quay về hệ thống cũ (Instant Rollback).

2. Branch by Abstraction (Nhánh hóa bằng lớp trừu tượng)

  • Cơ chế vận hành thực chiến: Tạo một lớp Interface bọc ngay phía trước đoạn code Legacy bên trong ứng dụng. Giữ nguyên Implementation Cũ đang chạy, đồng thời viết Implementation Mới sạch sẽ bên cạnh. Dùng cấu hình (Config/Flag) để chuyển đổi luồng gọi giữa 2 bên.
  • Bài toán áp dụng lý tưởng: Thay thế các thành phần nằm sâu bên trong lõi hệ thống (như đổi thư viện ORM, thay đổi cơ chế tính giá khuyến mãi, hoặc chuyển đổi nhà cung cấp dịch vụ SMS/Vận chuyển).
  • Chốt chặn an toàn (Safety Net): Cả code cũ và code mới cùng chung sống hòa bình trên nhánh main (Trunk-based development), tránh cảnh tạo nhánh Git riêng biệt suốt 3 tháng rồi dính "địa ngục Merge Conflict".

3. Shadow Traffic & Dark Launching (Chạy ngầm đối chiếu)

  • Cơ chế vận hành thực chiến: Luồng chính vẫn do code cũ xử lý và trả kết quả cho người dùng. Tuy nhiên, hệ thống nhân bản (Mirror/Async) request đó gửi ngầm sang code mới chạy song song, sau đó tự động so sánh kết quả trả về giữa Cũ và Mới (Diffing).
  • Bài toán áp dụng lý tưởng: Cải tổ các luồng tính toán tài chính, đối soát cước phí, hoặc nâng cấp câu truy vấn SQL phức tạp mà không được phép sai lệch dù chỉ 1 đồng.
  • Chốt chặn an toàn (Safety Net): Khi log đối chiếu cho thấy 1 triệu request liên tiếp có kết quả giữa Cũ và Mới khớp nhau 100% và độ trễ ổn định, lúc đó mới chính thức bật công tắc chuyển hẳn sang luồng mới.

4. Anti-Corruption Layer (ACL) (Lớp màng chống ô nhiễm)

  • Cơ chế vận hành thực chiến: Xây dựng một lớp biên dịch dữ liệu (Translator/Adapter) nằm giữa hệ thống mới và hệ thống cũ. Lớp này có nhiệm vụ chuyển đổi những cấu trúc bảng/JSON đặt tên lộn xộn của Legacy sang mô hình Domain sạch sẽ của hệ thống mới.
  • Bài toán áp dụng lý tưởng: Khi hệ thống mới bắt buộc phải giao tiếp hoặc đọc/ghi chung dữ liệu với một hệ thống ERP/Core cũ kỹ không thể sửa đổi.
  • Chốt chặn an toàn (Safety Net): Ngăn chặn tuyệt đối việc "mùi hôi" (Code Smells) và cấu trúc dữ liệu méo mó của hệ thống cũ lây lan sang làm hỏng kiến trúc của hệ thống mới.

3. Quy trình 5 bước tiếp cận và giải phẫu một "Vùng cấm địa" trong Codebase

Khi được giao nhiệm vụ vào sửa đổi hoặc tối ưu một hàm dài 2.500 dòng không một dòng chú thích, hãy tuân thủ kỷ luật 5 bước của Michael Feathers (Working Effectively with Legacy Code):

  1. Xác định "Điểm khâu" (Identify Seams): Tìm ra vị trí mà bạn có thể thay đổi hành vi của chương trình mà không cần sửa trực tiếp vào bên trong khối code rối rắm đó (thường là thông qua việc tách hàm nhỏ hoặc bọc một Interface ở điểm đầu vào/đầu ra).

  2. Đóng băng hành vi bằng Golden Master / Characterization Tests: Trước khi xóa bất kỳ dòng code nào, hãy chụp lại một tập hợp gồm 1.000 bộ dữ liệu đầu vào thực tế (Production Payloads) cùng kết quả đầu ra tương ứng của hệ thống cũ. Đây chính là "Bản mẫu vàng" (Golden Master) — sau khi bạn viết lại code mới, chạy lại đúng 1.000 bộ đầu vào này, kết quả phải giống hệt bản gốc đến từng trường JSON.

  3. Kỹ thuật "Scratch Refactoring" (Tái cấu trúc nháp để hiểu): Tạo một nhánh Git nháp, thoải mái đổi tên biến, tách hàm, xóa các đoạn code chết, viết ghi chú thẳng vào code để não bộ hiểu trọn vẹn luồng logic. Sau khi đã hiểu thấu đáo... hãy xóa nhánh nháp đó đi (git reset --hard), rồi mới bắt đầu viết lại từng bước nhỏ một cách cẩn trọng kèm bài test.

  4. Cắt tỉa cơ sở dữ liệu theo chiến thuật "Expand and Contract" (Mở rộng rồi Thu hẹp): Tuyệt đối không bao giờ đổi tên cột (RENAME COLUMN) hoặc sửa cấu trúc bảng trực tiếp trên hệ thống đang chạy vì sẽ làm chết các tiến trình code cũ đang truy cập. Hãy thêm cột mới →\rightarrow Viết code ghi song song vào cả cột cũ và cột mới (Dual-Write) →\rightarrow Chạy script đồng bộ dữ liệu lịch sử (Backfill) →\rightarrow Chuyển luồng đọc sang cột mới →\rightarrow Xóa cột cũ ở phiên bản sau cùng.

  5. Quy tắc Hướng đạo sinh (The Boy Scout Rule): "Luôn để lại khu cắm trại sạch hơn lúc bạn mới đến." Không cần xin sếp 2 tháng để Refactor toàn dự án; mỗi khi vào sửa một bug hoặc thêm một tính năng nhỏ, hãy tiện tay làm sạch, tách hàm và bổ sung test cho đúng khu vực xung quanh dòng code bạn vừa chạm tới. Lãi kép của sự sạch sẽ sẽ xuất hiện sau 3 tháng.

4. Vũ khí AI trong Cải tổ Legacy: Tự động hóa quá trình "Phẫu thuật" và "Đối chiếu"

Việc cải tổ hệ thống Legacy từng là công việc nhàm chán, căng thẳng và tốn nhiều mồ hôi nhất của kỹ sư Backend. Nhưng khi kết hợp với AI CLI và các mô hình ngôn ngữ lớn, tốc độ và độ an toàn của quá trình này tăng lên một bậc hoàn toàn mới:

  • Tự động tách hàm và trích xuất Interface (Automated Seam Extraction):

    • Đưa một hàm Legacy dài 800 dòng chứa lẫn lộn cả kiểm tra dữ liệu, tính toán tiền, lưu Database và gửi Email vào AI với câu lệnh: "Hãy áp dụng nguyên lý Single Responsibility và Branch by Abstraction. Không được làm thay đổi bất kỳ một chi tiết logic nghiệp vụ nào (kể cả các nhánh xử lý lỗi kỳ lạ), hãy tách các thao tác gọi Database và gửi Email ra thành các Interface độc lập, đồng thời giữ nguyên luồng tính toán lõi."
  • Sinh bộ dữ liệu kiểm thử "Golden Master" toàn diện:

    • Yêu cầu AI phân tích mã nguồn cũ để tìm ra toàn bộ các nhánh rẽ (Cyclomatic Complexity): "Hàm cũ này có 24 nhánh if-else và switch-case lồng nhau. Hãy thiết kế cho tôi một bộ dữ liệu đầu vào (JSON/Table-driven test) gồm đúng 24 trường hợp sao cho mỗi trường hợp kích hoạt chính xác 1 nhánh logic của hàm này, dùng làm lưới bảo hiểm trước khi tôi viết lại nó sang Golang/NestJS."
  • Viết công cụ đối chiếu Shadow Traffic (Automated Diffing Worker):

    • Khi áp dụng chiến thuật chạy song song Cũ và Mới, hãy nhờ AI viết nhanh một hàm bất đồng bộ (Goroutine / Background Job) nhận vào Output của hệ thống cũ và Output của hệ thống mới, tự động bỏ qua các trường biến động theo thời gian (như updated_at, request_id), so sánh sâu từng trường dữ liệu còn lại và bắn cảnh báo chi tiết lên Slack/Sentry nếu phát hiện bất kỳ sự sai lệch nào.

Tổng kết bài học

"Đập bỏ một tòa nhà cũ để xây lại trên bãi đất trống là việc ai có tiền cũng làm được; nhưng gia cố lại móng và thay thế từng cây cột chịu lực trong khi hàng chục ngàn người vẫn đang sinh hoạt bên trong tòa nhà đó mới là đỉnh cao của nghệ thuật kiến trúc kỹ thuật."


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí