Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn đang là Project Manager của một đội phát triển sản phẩm fintech ở TP.HCM. Sprint nào team cũng cam kết giao 8 user story, nhưng ba sprint gần đây velocity tụt còn 4-5 story, dù không ai nghỉ phép, không ai chuyển team. Khi bạn hỏi, các developer trả lời na ná nhau: "Code chỗ này động vào là vỡ chỗ khác", "Em sợ sửa hàm này lắm vì không biết ai còn đang gọi nó". Đó chính là dấu hiệu của một codebase đang xuống cấp về chất lượng nội tại — và Refactoring là công cụ chính để chữa căn bệnh đó.
Nhiều PM Việt Nam coi refactoring là "việc của developer", là thứ kỹ thuật thuần túy mà mình không cần hiểu. Đây là một sai lầm tốn kém. Bạn không cần biết viết code, nhưng bạn bắt buộc phải hiểu refactoring là gì, vì sao nó ảnh hưởng trực tiếp đến tốc độ giao hàng, đến chi phí thay đổi, và đến khả năng đáp ứng yêu cầu kinh doanh. Nếu không, bạn sẽ vô tình ép team đánh đổi chất lượng lấy tốc độ ngắn hạn, để rồi trả giá đắt gấp nhiều lần ở các sprint sau.
Trong bài này, chúng ta sẽ làm rõ định nghĩa chuẩn của refactoring theo Martin Fowler, phân biệt nó với "viết lại" hay "sửa bug", hiểu mối quan hệ giữa refactoring và chất lượng code, và quan trọng nhất — làm thế nào để bạn, với tư cách PM, tạo điều kiện và bảo vệ không gian cho việc này một cách bền vững.
Khái niệm cốt lõi
Refactoring là gì — định nghĩa chuẩn của Martin Fowler
Martin Fowler, tác giả cuốn "Refactoring: Improving the Design of Existing Code", định nghĩa: Refactoring là một thay đổi được thực hiện lên cấu trúc nội tại của phần mềm để làm cho nó dễ hiểu hơn và rẻ hơn khi sửa đổi, mà KHÔNG thay đổi hành vi quan sát được từ bên ngoài.
Hãy chú ý hai vế then chốt trong định nghĩa này:
- Thay đổi cấu trúc nội tại: ta sắp xếp lại tên biến, tách hàm, gom logic trùng lặp, đặt lại tên cho dễ đọc. Đây là việc "dọn dẹp ngôi nhà bên trong".
- Không thay đổi hành vi bên ngoài: người dùng cuối bấm nút vẫn ra kết quả y hệt. API trả về dữ liệu giống hệt. Refactoring không thêm tính năng, không sửa bug. Nếu hành vi thay đổi, đó không còn là refactoring thuần túy nữa.
Refactoring khác gì với Rewrite và Bug Fixing
Một bảng so sánh tinh thần để bạn nắm chắc:
- Refactoring: thay đổi nhỏ, từng bước, an toàn, hành vi giữ nguyên. Như sửa nhà từng phòng trong khi vẫn ở.
- Rewrite (viết lại): đập đi xây mới, rủi ro cao, thường mất nhiều tháng và hay thất bại. Như đập nhà xây lại từ móng.
- Bug fixing: thay đổi hành vi để sửa cái sai. Hành vi thay đổi có chủ đích.
Code Smell — mùi hôi của code
"Code smell" là thuật ngữ chỉ những dấu hiệu bề mặt cho thấy có vấn đề sâu hơn trong thiết kế. Code smell không phải là bug — code vẫn chạy đúng — nhưng nó báo hiệu code sẽ khó bảo trì. Một số smell phổ biến mà PM nên nghe quen tai:
- Duplicated Code (code trùng lặp): cùng một đoạn logic được copy-paste ở nhiều nơi. Sửa một chỗ phải nhớ sửa tất cả.
- Long Method (hàm quá dài): một hàm dài hàng trăm dòng làm quá nhiều việc.
- Large Class (lớp quá lớn): một class ôm đồm quá nhiều trách nhiệm.
- Long Parameter List: hàm nhận quá nhiều tham số, khó gọi đúng.
- Shotgun Surgery: muốn thay đổi một thứ nhỏ phải sửa rải rác ở rất nhiều file.
Mối quan hệ giữa Refactoring và Technical Debt
Bài trước trong khóa học đã bàn về tech debt ở góc độ chiến lược. Ở đây ta cần hiểu refactoring là cơ chế trả nợ cụ thể. Tech debt giống như lãi suất: mỗi ngày code xấu tồn tại, team trả thêm "lãi" dưới dạng thời gian sửa lâu hơn, lỗi nhiều hơn, người mới onboard chậm hơn. Refactoring là hành động trả gốc và lãi đó, biến codebase từ một gánh nặng thành một tài sản.
Vì sao chất lượng code là việc của PM
Có một mối liên hệ trực tiếp: chất lượng code nội tại quyết định chi phí thay đổi. Mà toàn bộ tinh thần Agile xoay quanh khả năng đáp ứng thay đổi nhanh chóng. Một codebase chất lượng kém khiến mỗi yêu cầu thay đổi từ phía business trở nên đắt đỏ — đúng nghĩa "đắt đỏ" cả về thời gian lẫn rủi ro. Khi đó, lợi thế cạnh tranh từ Agile bị xói mòn ngay từ gốc. Đó là lý do PM không thể đứng ngoài.
Tình huống thực tế
Tình huống 1 — Đội fintech Singapore và "tuần bảo trì" thảm họa
Một startup fintech tại Singapore (gọi là PayLah-clone để ẩn danh) có team 12 người làm ví điện tử. Trong hai năm, áp lực gọi vốn khiến CEO liên tục ép giao tính năng mới, không cho phép bất kỳ thời gian "dọn dẹp" nào. Velocity ban đầu 40 story point/sprint, sau 18 tháng tụt còn 18.
Khi tình hình quá tệ, ban lãnh đạo quyết định một giải pháp sai lầm cổ điển: dừng toàn bộ phát triển tính năng trong một sprint nguyên (2 tuần) để "refactor toàn bộ hệ thống". Kết quả: team cố viết lại nhiều module cùng lúc, hành vi bị thay đổi ngoài ý muốn, sau khi gộp code lại thì sản phẩm sập trong production hai ngày liền, mất khoảng 60.000 SGD doanh thu giao dịch.
Bài học rút ra: Refactoring quy mô lớn "big bang" gần như luôn thất bại. Đó thực chất là rewrite trá hình, vi phạm nguyên tắc "thay đổi nhỏ, từng bước". Cách đúng là refactoring liên tục, nhỏ giọt, đan xen vào công việc tính năng hằng ngày — chứ không phải dồn lại thành một "đại tu" hoành tráng đầy rủi ro. PM lẽ ra nên bảo vệ một tỉ lệ nhỏ thời gian mỗi sprint cho việc dọn dẹp ngay từ đầu.
Tình huống 2 — Boy Scout Rule tại một công ty gia công ở Hà Nội
Một công ty outsourcing ở Hà Nội nhận dự án bảo trì hệ thống quản lý kho cho khách Nhật. Codebase do team cũ để lại đầy code smell. PM mới — chị Lan — không xin được "ngân sách refactoring" riêng vì hợp đồng tính theo tính năng. Thay vì đấu tranh xin một dự án refactoring lớn, chị áp dụng "Boy Scout Rule" của Robert C. Martin: "Luôn để lại đoạn code sạch hơn so với lúc bạn tìm thấy nó."
Quy ước team đặt ra rất giản dị: mỗi khi developer chạm vào một file để làm tính năng, họ dọn dẹp nhẹ ngay vùng đó — đặt lại một cái tên biến tối nghĩa, tách một hàm dài, xóa code chết. Mỗi lần chỉ tốn 15-20 phút, ẩn trong estimate của task. Sau 6 tháng, không có "dự án refactoring" nào nhưng tỉ lệ bug giảm 35%, và thời gian onboard developer mới giảm từ 3 tuần xuống 1,5 tuần.
Bài học rút ra: Refactoring không cần phải là một dòng việc tách biệt cần ai phê duyệt. Khi được tích hợp như một thói quen kỹ thuật liên tục — "dọn dẹp lúc đi ngang qua" — nó vừa bền vững vừa không gây xung đột với áp lực giao hàng. Vai trò PM là tạo văn hóa và bảo vệ thói quen đó, không phải xin tiền cho từng đợt.
Tình huống 3 — Refactoring an toàn nhờ test tại Tiki
Một team thuộc nền tảng thương mại điện tử lớn của Việt Nam (bối cảnh giả định kiểu Tiki) cần thay đổi logic tính phí vận chuyển vốn nằm rải rác trong một hàm dài 400 dòng. Trước khi đụng vào, team dành 2 ngày viết bộ test bao phủ toàn bộ hành vi hiện tại của hàm đó — tất cả các trường hợp tính phí. Chỉ khi test xanh hết, họ mới bắt đầu tách hàm lớn thành các hàm nhỏ rõ ràng, mỗi bước nhỏ lại chạy test để chắc chắn hành vi không đổi.
Kết quả: việc refactoring kéo dài 3 ngày nhưng không gây ra một lỗi production nào, và sau đó việc thêm quy tắc phí mới cho dịp sale 9/9 chỉ mất nửa ngày thay vì hai ngày như trước.
Bài học rút ra: Refactoring an toàn bắt buộc đi kèm test. Test chính là "lưới an toàn" giúp xác nhận hành vi bên ngoài không đổi sau mỗi thay đổi nội tại. Là PM, khi nghe team xin thời gian viết test trước khi refactoring, đừng coi đó là chậm trễ — đó là khoản đầu tư giúp refactoring trở nên an toàn thay vì đánh bạc.
Hướng dẫn từng bước
Dưới đây là cách một PM nên đồng hành cùng việc refactoring trong vòng đời Agile:
- Tạo lưới an toàn bằng test trước: Đảm bảo vùng code sắp refactoring có test bao phủ hành vi hiện tại. Không có test thì refactoring là mạo hiểm. Đây là điều kiện tiên quyết.
- Refactoring theo bước nhỏ, có kiểm chứng liên tục: Khuyến khích team đi từng bước nhỏ — tách một hàm, đổi một tên — và chạy test sau mỗi bước. Tránh "đại tu" gộp nhiều thay đổi.
- Tách bạch refactoring và thêm tính năng: Đảm bảo commit/pull request refactoring không trộn lẫn việc thay đổi hành vi. Điều này giúp review dễ và truy vết lỗi nhanh.
- Tích hợp refactoring vào dòng chảy bình thường: Áp dụng Boy Scout Rule. Coi việc dọn dẹp nhẹ là một phần của Definition of Done cho mỗi user story, ẩn trong estimate, không cần dòng việc riêng.
- Dành dung lượng cố định cho việc trả nợ kỹ thuật: Thống nhất với team và stakeholder một tỉ lệ — ví dụ 15-20% năng lực mỗi sprint — cho cải thiện chất lượng. Đưa các hạng mục refactoring lớn hơn vào backlog và refinement.
- Làm cho chất lượng trở nên hữu hình: Cùng team theo dõi vài chỉ số đơn giản (tỉ lệ bug, thời gian sửa, độ phủ test, code smell từ công cụ phân tích tĩnh như SonarQube) để chứng minh giá trị của refactoring với stakeholder bằng dữ liệu.
- Bảo vệ không gian này trước áp lực deadline: Khi business ép giao nhanh, vai trò PM là diễn giải hậu quả của việc bỏ qua chất lượng — "nếu cắt phần này, ba sprint sau ta sẽ chậm gấp đôi" — chứ không phải im lặng đồng ý.
Lỗi thường gặp & mẹo
Lỗi 1 — Coi refactoring là "xa xỉ phẩm" chỉ làm khi rảnh. Thực tế, code không bao giờ "rảnh để dọn", và việc trì hoãn chỉ khiến nợ chồng nợ. Mẹo: gắn refactoring vào Definition of Done, biến nó thành mặc định chứ không phải lựa chọn.
Lỗi 2 — Cho phép "refactoring big bang". Dồn lại thành một đợt đại tu lớn là công thức thất bại. Mẹo: chia nhỏ, làm liên tục, ưu tiên vùng code thay đổi thường xuyên nhất (vì đó là nơi cho lợi tức cao nhất).
Lỗi 3 — Refactoring mà không có test. Đây là đánh bạc với production. Mẹo: yêu cầu lưới an toàn test trước khi đụng vào code chưa được bao phủ.
Lỗi 4 — Trộn refactoring với tính năng trong cùng một thay đổi. Khi có lỗi, không ai biết do đâu. Mẹo: thực thi "hai chiếc mũ" của Fowler, tách commit/PR rõ ràng.
Lỗi 5 — Để developer "refactoring" thành cái cớ để viết lại theo sở thích cá nhân. Refactoring không phải để code "đẹp theo gu" mà để rẻ hơn khi sửa đổi. Mẹo: luôn neo việc refactoring vào một mục tiêu cụ thể — sắp tới sẽ thay đổi vùng này, nên dọn để việc đó rẻ hơn.
Mẹo vàng cho PM: Đừng bao giờ hỏi "khi nào team sẽ ngừng refactoring để tập trung làm tính năng?". Câu hỏi đúng là "làm sao để chất lượng code giúp ta giao tính năng nhanh hơn một cách bền vững?". Sự khác biệt trong cách đặt câu hỏi này định hình cả văn hóa kỹ thuật của team.
Bài tập thực hành
- Bài tập nhận diện: Tham gia một buổi backlog refinement của team bạn (hoặc một team giả định). Yêu cầu developer chỉ ra 3 vùng code mà họ "ngại đụng vào nhất". Ghi lại lý do. Đối chiếu với danh sách code smell ở trên xem chúng thuộc loại nào.
- Bài tập phân biệt: Cho ba phát biểu sau, hãy phân loại đâu là refactoring, đâu là rewrite, đâu là bug fixing: (a) "Tách hàm tính giá 300 dòng thành 5 hàm nhỏ, kết quả trả về y hệt." (b) "Sửa lỗi tính sai thuế VAT cho đơn hàng nước ngoài." (c) "Bỏ toàn bộ module cũ, viết lại bằng framework mới trong 3 tháng."
- Bài tập chính sách: Soạn một đề xuất ngắn (nửa trang) thuyết phục Product Owner dành 15% năng lực mỗi sprint cho việc cải thiện chất lượng code. Dùng ngôn ngữ kinh doanh — chi phí thay đổi, tốc độ giao hàng, rủi ro — chứ không dùng thuật ngữ kỹ thuật.
- Bài tập áp dụng Boy Scout Rule: Cùng team thử nghiệm trong một sprint quy ước "mỗi file chạm vào phải để lại sạch hơn". Cuối sprint, thu thập cảm nhận của team và xem có ảnh hưởng tiêu cực đến velocity cam kết không.
Tóm tắt
Refactoring, theo định nghĩa của Martin Fowler, là việc thay đổi cấu trúc nội tại của code để nó dễ hiểu và rẻ hơn khi sửa đổi, mà không thay đổi hành vi bên ngoài. Nó khác hẳn rewrite (đập đi xây lại) và bug fixing (thay đổi hành vi có chủ đích). Refactoring là cơ chế cụ thể để trả technical debt và là phản ứng có hệ thống trước các "code smell".
Với tư cách PM, bạn cần nhớ: chất lượng code nội tại quyết định chi phí thay đổi, mà chi phí thay đổi quyết định liệu Agile của bạn có thực sự nhanh hay không. Hãy bảo vệ không gian cho refactoring liên tục, nhỏ giọt; yêu cầu lưới an toàn test; tách bạch refactoring với tính năng; áp dụng Boy Scout Rule; và làm cho chất lượng trở nên hữu hình bằng dữ liệu. Tránh "đại tu big bang" — bài học xương máu từ những thất bại thực tế. Refactoring không phải xa xỉ phẩm, mà là khoản đầu tư giữ cho tốc độ giao hàng bền vững theo thời gian.