Tôi Đã Refactor 50.000 Dòng Code Legacy Như Thế Nào?
Refactor 50.000 dòng code legacy nghe giống một quyết định liều lĩnh. Nhưng vấn đề thật ra không nằm ở số dòng. Nó nằm ở số hành vi ẩn trong hệ thống mà không ai còn dám đụng vào. Google cho biết kỹ sư mới có thể mất khoảng 6 tháng để ramp-up trong một codebase lớn (Google SWE Book, 2020). Nếu việc hiểu hệ thống đã tốn đến vậy, viết lại từ đầu thường chỉ đổi rủi ro cũ lấy rủi ro mới. Trong bài này, tôi chia sẻ cách tôi tiếp cận một đợt refactor lớn theo hướng đọc trước, khóa rủi ro trước, rồi mới sửa từng lớp nhỏ.
Điểm chính
- Tôi không bắt đầu bằng rewrite, vì Google cho biết kỹ sư mới có thể mất 6 tháng để hiểu một hệ thống lớn.
- Tôi chia 50.000 dòng code thành vùng an toàn, vùng hay thay đổi, và vùng chưa đủ hiểu để tránh refactor mù.
- Tôi dùng test như lưới an toàn, bám theo khuyến nghị 80% unit test và 20% broader-scoped test của Google.
Vì sao tôi không rewrite từ đầu?
Google cho biết kỹ sư mới có thể mất khoảng 6 tháng để ramp-up trong một codebase lớn (Google SWE Book, 2020). Vì vậy, với 50.000 dòng code legacy, viết lại từ đầu hiếm khi là đường ngắn nhất. Tôi chọn mục tiêu thực tế hơn: làm hệ thống dễ hiểu hơn sau mỗi lần chạm vào.
Điều đầu tiên tôi làm không phải mở editor để xóa code. Tôi mở bug backlog, pull request cũ, log production và các file bị sửa lặp lại nhiều nhất. Nếu một module vừa khó đọc vừa thường xuyên gây lỗi, đó mới là điểm bắt đầu. Bạn có thật sự cần đụng vào cả khối đá lớn ngay lập tức không? Thường là không.
Trong các đợt refactor lớn, phần khó nhất không phải đổi tên hàm hay tách file. Phần khó nhất là chống lại cảm giác muốn “làm sạch hết một lần”. Tôi chỉ cho phép mình sửa những gì có thể giải thích lại bằng một sơ đồ đơn giản trong vài phút. Nếu chưa giải thích được, tôi coi như chưa hiểu đủ để sửa.
Một codebase lớn không gãy vì quá nhiều file; nó gãy vì thời gian hiểu hành vi quá dài. Khi Google nói thời gian ramp-up có thể kéo dài 6 tháng (Google SWE Book, 2020), bài học rõ ràng là refactor nên tối ưu khả năng giải thích, không phải số dòng bị xóa.
Tôi chia 50.000 dòng code thành 4 lớp rủi ro như thế nào?
Khoảng 20% Google engineers đang ở trong quy trình readability tại một thời điểm bất kỳ, trong khi chỉ khoảng 1-2% là readability reviewers (Google SWE Book, 2020). Con số này cho thấy code dễ đọc không tự xuất hiện. Với code legacy, tôi phải chủ động chia nó thành từng lớp rủi ro trước khi đổi cấu trúc.
Tôi dùng một cách chia rất thực dụng. Lớp đầu là luồng chính đang chạy ổn định và sinh doanh thu. Lớp hai là các điểm hay thay đổi, thường nằm ở validation, mapping dữ liệu và quyền truy cập. Lớp ba là phần trùng lặp, nơi bạn có thể gom logic mà ít chạm nghiệp vụ. Lớp bốn là vùng mù: code ít người hiểu, ít test, nhưng ảnh hưởng lớn. Vùng mù không phải nơi nên “thể hiện kỹ thuật”. Nó là nơi phải đi chậm.
Tôi không ưu tiên file xấu nhất. Tôi ưu tiên file làm cả đội chậm nhất. Nếu một helper dài 700 dòng nhưng ít ai đụng, tôi để sau. Nếu một service chỉ 120 dòng nhưng tuần nào cũng có người mở lại để dò bug, nó lên đầu danh sách. Refactor tốt là giảm ma sát của team, không phải săn cho đủ mớ code xấu.
Theo Google, khoảng 33% truy vấn tìm code là để trả lời câu hỏi “How?” (Google SWE Book, 2020). Điều đó có nghĩa là nơi đáng refactor nhất thường là nơi mọi người phải đi tìm ví dụ vận hành, không phải nơi nhìn “bẩn” nhất trên bề mặt.
[INTERNAL-LINK: cách ưu tiên technical debt → bài hướng dẫn xếp hạng debt theo tác động]
Tôi dùng test như lưới an toàn ra sao?
Google khuyến nghị hệ thống test nên nghiêng về khoảng 80% unit test và 20% broader-scoped test (Google SWE Book, 2020). Với tôi, đó là câu trả lời ngắn gọn nhất cho câu hỏi “refactor lớn mà không vỡ production bằng cách nào?”. Không có lưới an toàn, bạn chỉ đang đổi bug cũ sang bug mới.

Tôi không cố phủ test toàn bộ ngay từ đầu. Tôi viết characterization tests cho hành vi đang tồn tại, kể cả khi hành vi đó hơi kỳ quặc. Sau đó, tôi thêm vài smoke tests ở những luồng quan trọng nhất như đăng nhập, tạo dữ liệu, gửi thông báo hoặc tính tiền. Chỉ cần chừng đó, mỗi PR refactor đã bớt đáng sợ đi rất nhiều.
Một nguyên tắc tôi giữ rất chặt là PR nhỏ. Nếu một thay đổi làm diff phình to đến mức reviewer phải cuộn mỏi tay, tôi tách nó ra. Google mô tả rằng một thay đổi đơn lẻ trong hệ thống lớn có thể kích hoạt hàng trăm nghìn tests (Google SWE Book, 2020). Vậy nên, thay đổi càng hẹp, khả năng khoanh lỗi càng cao.
Refactor an toàn không đòi hỏi test hoàn hảo ngay ngày đầu. Nó đòi hỏi đúng test ở đúng chỗ. Khuyến nghị 80/20 của Google là lời nhắc rất thực tế: phần lớn niềm tin nên đến từ unit tests nhanh, nhưng vài broader-scoped tests mới là thứ ngăn bạn phá vỡ hành vi quan trọng.
Kết quả lớn nhất không phải ít dòng hơn
Google ước tính nội bộ có khoảng 1.000.000 truy vấn tìm code mỗi ngày, và 33% trong số đó là đi tìm lời giải cho câu hỏi “How?” (Google SWE Book, 2020). Vì vậy, với tôi, kết quả lớn nhất của refactor không phải là xóa được bao nhiêu dòng. Nó là giảm số lần cả đội phải dò mò để hiểu hệ thống.

Sau refactor, tôi tự kiểm tra bằng ba câu hỏi rất đơn giản. Người mới có giải thích được luồng chính sau một buổi pairing không? Reviewer có còn hỏi lại cùng một điểm ở ba PR liên tiếp không? Và khi bug xuất hiện, tôi có tìm được nơi sửa trong vài phút thay vì vài giờ không? Nếu ba câu trả lời đều tốt hơn, đợt refactor đó có giá trị.
Một codebase khỏe không phải codebase “đẹp” theo nghĩa trình diễn. Nó là codebase mà người mới vào không cần sáu tháng để bớt sợ, reviewer không cần giải mã từng hàm, và team không phải mở cùng một file chỉ để nhớ hệ thống đang hoạt động ra sao.
Câu hỏi thường gặp
Không có test thì có nên refactor không?
Có, nhưng nên bắt đầu rất hẹp. Google khuyến nghị tỷ lệ test khoảng 80% unit và 20% broader-scoped tests (Google SWE Book, 2020). Nếu chưa có gì, hãy viết characterization test cho một hành vi quan trọng trước, rồi refactor đúng vùng đó.
Tôi nên chọn chỗ nào để refactor trước?
Hãy chọn nơi gây ma sát cao nhất, không phải nơi xấu nhất. Google cho biết khoảng 33% truy vấn tìm code là để trả lời “How?” (Google SWE Book, 2020). Nếu cả team liên tục phải hỏi một luồng hoạt động thế nào, đó là điểm nên sửa trước.
Bao giờ thì rewrite toàn bộ hợp lý hơn?
Rewrite chỉ hợp lý khi kiến trúc hiện tại chặn đường kinh doanh và bạn có kế hoạch chuyển đổi theo từng bước. Google cũng cho thấy kỹ sư mới có thể mất 6 tháng để ramp-up trong hệ thống lớn (Google SWE Book, 2020), nên viết lại toàn bộ thường làm chi phí hiểu hệ thống tăng mạnh trước khi giảm.
Kết luận
Refactor 50.000 dòng code legacy không bắt đầu bằng tham vọng “làm cho đẹp”. Nó bắt đầu bằng kỷ luật: hiểu hành vi trước, chia nhỏ rủi ro, dựng lưới an toàn, rồi mới động tay. Nếu bạn đang ngồi trước một module cũ và chưa biết bắt đầu từ đâu, hãy thử đúng một bước nhỏ trong tuần này: chọn một luồng hay lỗi, viết test tối thiểu cho nó, rồi refactor phần đó đến mức bạn có thể giải thích lại trong vài phút. Chỉ vậy thôi đã là tiến bộ thật rồi.
Nguồn tham khảo
- Google SWE Book — Chương
Readability at Google: https://abseil.io/resources/swe-book/html/ch06.html - Google SWE Book — Chương
Unit Tests, Broader-Scoped Tests, and the Test Pyramid: https://abseil.io/resources/swe-book/html/ch12.html - Google SWE Book — Chương
Code Search: https://abseil.io/resources/swe-book/html/ch15.html
Bình luận