Nợ kỹ thuật: hiểu đúng để trả dần trước khi nó nhấn chìm dự án

Gần như mọi lập trình viên đều từng nghe câu “chỗ này làm tạm đã, sau sửa sau”. Lời hứa “sửa sau” đó chính là hình hài đầu tiên của nợ kỹ thuật. Cũng như nợ tài chính, nợ kỹ thuật không hẳn xấu: đôi khi vay để đi nhanh là một quyết định khôn ngoan. Vấn đề nằm ở chỗ nhiều đội vay mà không ghi sổ, không trả lãi, để rồi một ngày phát hiện mỗi tính năng mới đều tốn gấp đôi thời gian và ai cũng sợ đụng vào phần code cũ. Bài viết này giúp bạn nhìn nợ kỹ thuật như một thứ có thể quản lý được, thay vì một tai họa mơ hồ không ai dám gọi tên.
Nợ kỹ thuật thật ra là gì
Nợ kỹ thuật là khoảng cách giữa cách phần mềm đang được viết và cách đáng lẽ nó nên được viết để dễ thay đổi trong tương lai. Khoảng cách đó phát sinh từ nhiều nguồn: một quyết định vội vàng để kịp hạn, một thư viện chọn sai từ đầu, một mảng nghiệp vụ đã thay đổi nhưng code chưa cập nhật theo, hay đơn giản là hiểu biết của cả đội về bài toán đã trưởng thành hơn nhiều so với lúc viết những dòng đầu tiên.
Điều quan trọng cần phân biệt là không phải mọi đoạn code xấu đều là nợ có chủ đích. Có loại nợ ta chủ động vay vì lý do hợp lý, và có loại nợ hình thành do cẩu thả hoặc thiếu kỹ năng. Cách xử lý hai loại này khác nhau: loại đầu cần được ghi nhận và lên kế hoạch trả, loại sau cần được ngăn ngừa bằng học hỏi và quy trình tốt hơn. Gọi tên đúng giúp đội không đổ lỗi lẫn nhau mà tập trung vào giải pháp.
Vì sao nợ tích tụ mà không ai để ý
Nợ kỹ thuật nguy hiểm vì nó gần như vô hình với những người không đọc code hằng ngày. Khách hàng nhìn thấy tính năng chạy được, quản lý nhìn thấy task đóng đúng hạn, nhưng không ai nhìn thấy phần móng đang mục dần bên dưới. Lãi của khoản nợ này được trả bằng thời gian: mỗi khi thêm tính năng, kỹ sư phải len lỏi qua mớ code rối, phải nơm nớp kiểm tra xem mình có làm hỏng chỗ khác không, phải giải thích cho người mới vì sao chỗ này lại kỳ lạ đến vậy.
Vài dấu hiệu cho thấy nợ đang tích tụ đến mức đáng lo:
- Ước lượng thời gian ngày càng phình to cho những thay đổi tưởng chừng nhỏ nhặt.
- Lỗi cứ sửa chỗ này lại bung ra chỗ khác, không ai dám khẳng định sửa xong là thật sự xong.
- Chỉ một vài người trong đội dám đụng vào một số phần nhất định của hệ thống.
- Người mới mất rất lâu mới bắt đầu đóng góp được, vì code không tự giải thích được chính nó.
Trả nợ mà không dừng cả dự án
Sai lầm phổ biến là đợi đến khi mọi thứ quá tệ rồi mới xin một khoảng thời gian dài để “viết lại toàn bộ”. Những đợt viết lại lớn thường thất bại vì chúng dừng dòng chảy giá trị cho khách hàng trong nhiều tháng, và phiên bản mới lại đẻ ra những khoản nợ mới của riêng nó. Chiến lược bền vững hơn là trả nợ liên tục thành từng phần nhỏ, hòa vào công việc hằng ngày.
Một nguyên tắc thực dụng là quy tắc “để lại sạch hơn lúc đến”: mỗi khi bạn động vào một vùng code vì một lý do khác, hãy tranh thủ dọn dẹp một chút ngay tại đó, đặt lại một cái tên rối rắm, tách một hàm quá dài, thêm một bài kiểm thử còn thiếu. Cách này khiến việc trả nợ tập trung vào đúng những chỗ đang thực sự được đụng đến, tức là những chỗ đáng đầu tư nhất, thay vì lãng phí công sức làm đẹp phần code chẳng ai chạm tới.
Tái cấu trúc an toàn
Tái cấu trúc là thay đổi cấu trúc bên trong của code mà không làm thay đổi hành vi bên ngoài. Cụm từ “không thay đổi hành vi” là điểm mấu chốt, và nó đòi hỏi một tấm lưới an toàn. Tấm lưới đó chính là bộ kiểm thử tự động: nếu bạn có thể chạy một loạt bài kiểm thử để khẳng định rằng sau khi dọn dẹp mọi thứ vẫn hoạt động như cũ, bạn mới dám mạnh tay tái cấu trúc. Không có lưới, mỗi lần đụng vào code cũ đều là một canh bạc.
Vài nguyên tắc giúp việc tái cấu trúc bớt rủi ro:
- Đi từng bước nhỏ, kiểm tra sau mỗi bước, thay vì đập đi xây lại cả một mảng lớn trong một lần.
- Tách rõ commit dọn dẹp và commit thêm tính năng, để khi có sự cố còn dễ lần ra nguyên nhân.
- Khi phần code chưa có kiểm thử, hãy viết vài bài kiểm thử bao quanh hành vi hiện tại trước, rồi mới bắt đầu dọn dẹp.
- Ưu tiên tái cấu trúc những chỗ vừa hay bị lỗi vừa hay bị sửa, vì đó là nơi khoản đầu tư sinh lời nhanh nhất.
Đưa nợ kỹ thuật ra ánh sáng
Điều tệ nhất với nợ kỹ thuật là để nó nằm im trong đầu vài kỹ sư. Muốn quản lý được, phải làm cho nó hữu hình. Nhiều đội ghi nợ kỹ thuật thành các mục công việc rõ ràng, mô tả cái giá đang phải trả và lợi ích khi khắc phục, rồi đưa chúng vào cùng danh sách với các tính năng để cùng cân nhắc thứ tự ưu tiên. Khi cái giá của việc không làm gì được nói ra bằng ngôn ngữ thời gian và rủi ro, người ra quyết định phi kỹ thuật mới có cơ sở để đồng ý đầu tư.
Suy cho cùng, quản lý nợ kỹ thuật không phải là theo đuổi sự hoàn hảo. Một dự án sống động luôn có nợ, cũng như một gia đình vẫn có thể vay để mua nhà. Điều tạo nên khác biệt là biết mình đang nợ bao nhiêu, nợ ở đâu, và có một kế hoạch trả đều đặn để khoản nợ ấy không bao giờ lớn đến mức bóp nghẹt khả năng tiến về phía trước của cả đội.