Pragmatic Programmer – Topic 40: Refactoring — Dám đụng vào code cũ
Có một câu nói trong Pragmatic Programmer mà mình nhớ mãi: "Programming is the act of turning a specification into an executable form, but that's not the whole story. Programming is also about managing complexity." Và cái cách quản lý độ phức tạp tốt nhất mà mình biết, chính là refactoring.
Hồi mới đi làm, mình cũng từng rất ngại đụng vào code cũ. Kiểu "nó đang chạy được rồi, đừng sờ vô kẻo hư". Cảm giác đó quen lắm đúng không? Nhưng rồi càng để lâu, cái code đó càng trở nên khó hiểu, khó sửa, và mỗi lần thêm tính năng mới là mỗi lần khổ sở.
Ảnh: Nimit Kansagra — Pexels
Code như khu vườn, không phải toà nhà
Điều đầu tiên mà sách dạy mình về refactoring là thay đổi tư duy. Đừng nghĩ code là một toà nhà — xây xong là xong, chỉ sửa khi có lỗi. Hãy nghĩ nó như một khu vườn. Bạn phải tưới nước, nhổ cỏ, tỉa cành hằng ngày. Có những cây mọc sai chỗ thì phải đánh đi trồng lại.
Refactoring cũng vậy. Nó là hoạt động liên tục, không phải một dự án riêng. Mỗi ngày một tí, mỗi lần sửa một tí. Đừng để code hư tới mức phải có "dự án refactoring kéo dài 3 tháng" — tới lúc đó thì mọi thứ đã quá muộn rồi.
Khi nào nên refactor?
Sách đưa ra mấy dấu hiệu rất thực tế:
- Duplication — bạn thấy cùng một logic lặp lại ở 3-4 chỗ
- Non-orthogonal design — sửa một chỗ nhưng ảnh hưởng tới cả tá nơi khác
- Outdated knowledge — code còn dùng pattern cũ, trong khi team đã chuyển qua cách mới
- Performance issues — code chạy chậm vì thiết kế không phù hợp
- Code smells — cảm giác "hình như chỗ này có vấn đề" (listen to your lizard brain!)
Có một quy tắc mình rất thích gọi là "Campground Rule": luôn để code sạch hơn lúc bạn mới tới. Không cần refactor cả thành. Chỉ cần mỗi lần sửa một function, hãy làm nó sạch hơn một tí. Tích tiểu thành đại.
Cách refactor an toàn
Sách nhấn mạnh: refactoring là cải thiện cấu trúc code, không phải thay đổi hành vi. Nghĩa là kết quả đầu ra phải y hệt như cũ, chỉ có đường ống bên trong là gọn gàng hơn.
Mấy nguyên tắc vàng:
Refactor từng bước nhỏ. Đừng ôm đồm. Mỗi lần chỉ đổi một biến, tách một hàm, đổi tên một class. Commit sau mỗi bước. Nếu sai, git revert là về lại.
Giữ code luôn chạy được. Đây là cái khó nhất. Refactoring thường làm code "hỏng" tạm thời. Nhưng bạn càng thu nhỏ bước refactor, code càng nhanh quay lại trạng thái chạy được. Lý tưởng là mỗi 5-10 phút, code lại compile và test pass.
Không thêm tính năng khi đang refactor. Đây là lỗi kinh điển nhất. "À đang sửa cái này, tiện thể thêm luôn feature này cho nhanh." Sai! Refactoring và feature addition là hai việc khác nhau. Nếu muốn thêm tính năng, commit refactor xong rồi mới thêm.
Test là tấm lưới an toàn. Trước khi refactor, hãy chắc chắn bạn có test. Nếu code không có test, thì việc đầu tiên là viết test trước (refactor để code dễ test, nếu cần). Chạy test sau mỗi bước refactor, nếu test xanh thì yên tâm.
Ảnh: Daniil Komov — Pexels
Kinh nghiệm xương máu
Hồi mới học refactor, mình từng phạm sai lầm là thấy một function 200 dòng, liền xoá hết viết lại từ đầu. Kết quả là 2 ngày sau mới làm xong, mà còn miss mấy edge case. Đau thiệt.
Giờ mình làm khác: nếu gặp function khó đọc, mình bắt đầu bằng cách đổi tên biến cho dễ hiểu. Rồi tách các đoạn độc lập thành function riêng. Từng bước một, chậm mà chắc. Sau 30 phút, function 200 dòng kia biến thành 6 function nhỏ, mỗi cái 20-30 dòng, dễ đọc, dễ test. Chưa đầy 1 tiếng, mọi thứ xong xuôi. Không bug, không stress.
Lời kết
Refactoring không phải là tái thiết (rewrite). Nó là chăm sóc code hằng ngày. Một team tốt không phải là team viết code mới nhanh nhất, mà là team biết giữ cho code cũ luôn sạch và healthy.
Cuối cùng, trích câu nói mình thích nhất trong topic này: "Make it work, make it right, make it fast." Đừng nhảy thẳng tới "make it fast" mà quên mất "make it right".
📋 Phụ lục thuật ngữ
- Refactoring — cải thiện cấu trúc code mà không thay đổi hành vi bên ngoài
- Code smell — dấu hiệu cho thấy code có vấn đề về thiết kế
- Campground Rule — nguyên tắc để code sạch hơn lúc bạn tới
- Non-orthogonal design — thiết kế thiếu độc lập, sửa chỗ này ảnh hưởng chỗ khác
- Test coverage — phần trăm code được bao phủ bởi test, là tấm lưới an toàn khi refactor