Đến nội dung chính
Thư viện GKNơi khơi nguồn tò mò
Tiếng Việt

Lãi phải trả cho mã viết vội: Nợ kỹ thuật (Technical Debt)

Khi vội vàng đóng đồ chuyển nhà, đôi khi chúng ta cứ cho đồ vào thùng một cách tùy tiện rồi tự nhủ sẽ sắp xếp sau. Trước mắt thì xong nhanh, nhưng khi đến nhà mới, muốn tìm một món đồ lại phải lục tung vài chiếc thùng, và càng trì hoãn việc sắp xếp thì công sức bỏ ra càng lớn. Trong phát triển phần mềm cũng xảy ra điều tương tự, và thuật ngữ dùng để chỉ hiện tượng này chính là “nợ kỹ thuật (Technical Debt)”.

Nói một cách đơn giản, nợ kỹ thuật là chi phí bổ sung phải trả về sau để sửa chữa và hoàn thiện khi ta chọn cách làm dễ dàng thay cho một thiết kế tốt hơn nhằm đạt tốc độ trước mắt. Đây là phép ví von được lập trình viên người Mỹ Ward Cunningham sử dụng lần đầu trong một bài trình bày tại hội nghị năm 1992, và ngày nay đã trở thành thuật ngữ phổ biến không chỉ với lập trình viên mà còn với cả ban lãnh đạo và người lập kế hoạch.

Tài liệu này trình bày một cách dễ hiểu từ ý nghĩa và bối cảnh ra đời của nợ kỹ thuật đến cơ chế tích lũy nợ, các loại nợ khác nhau, cùng những phương pháp quản lý và từng bước trả nợ.


Nguyên lý và cách quản lý nợ kỹ thuật

Nợ kỹ thuật là gì?

Đồ họa thông tin về nợ kỹ thuật. Giải thích rằng những lựa chọn vội vàng hoặc sự thiếu hiểu biết bộc lộ trong quá trình phát triển có thể khiến việc thay đổi về sau trở nên khó khăn. Qua một ví dụ giả định về việc sao chép quy tắc giảm giá vào ba nơi, đồ họa phân biệt gánh nặng bổ sung khi phải sửa và kiểm tra nhiều nơi mỗi lần thay đổi quy tắc là tiền lãi, còn công sức cải thiện cấu trúc là tiền gốc. Đồ họa đề xuất xác định thứ tự ưu tiên dựa trên tần suất và tác động của thay đổi, cải thiện từng bước thông qua kiểm thử và đưa những hiểu biết mới vào thiết kế, đồng thời nhấn mạnh rằng không phải mọi đoạn mã cũ và lỗi phần mềm đều là nợ kỹ thuật.
Bàn làm việc bừa bộn của lập trình viên với máy tính xách tay, giấy ghi chú, cà phê đã nguội và dây cáp rối vào nhau

Năm 1992, tại OOPSLA, một hội nghị về lập trình hướng đối tượng, Cunningham đã đưa ra phép ví von này khi trình bày kinh nghiệm phát triển phần mềm tài chính do mình xây dựng. Ông nói rằng mã được đưa ra lần đầu cũng giống như việc vay nợ. Vay một chút có thể đẩy nhanh tốc độ phát triển, nhưng khoản nợ ấy phải được trả đúng lúc bằng cách sửa và viết lại mã.

Phép ví von này lan truyền rộng rãi vì ngay cả những người không phải lập trình viên cũng có thể hiểu ngay. Giống như khoản nợ phát sinh lãi, nếu cứ để nguyên mã viết qua loa thì mỗi lần bổ sung một chức năng, thời gian và công sức cần bỏ ra lại tăng thêm một chút. Cunningham cảnh báo rằng khi những khoản nợ chưa trả tích tụ, cả tổ chức có thể bị tiền lãi đè nặng đến mức đình trệ.

Ở đây, tiền gốc chính là phần mã cẩu thả chưa được sửa, còn tiền lãi là thời gian và công sức phải bỏ thêm mỗi lần vì phần mã đó. Trả tiền gốc có nghĩa là tổ chức lại mã cho đúng cách, và chừng nào vẫn còn tiền gốc thì tiền lãi vẫn đều đặn phát sinh trong suốt quá trình phát triển.

Nợ tích lũy và lãi phát sinh như thế nào?

Những sợi cáp mạng nhiều màu tuôn ra từ tủ máy chủ trong tình trạng rối vào nhau

Nợ kỹ thuật tích lũy qua nhiều con đường. Những trường hợp điển hình là bỏ qua kiểm thử để kịp lịch phát hành, sao chép rồi dán những đoạn mã tương tự thay vì chỉnh sửa chúng, hoặc không để lại tài liệu hướng dẫn. Cũng có những khoản nợ phát sinh dù không ai làm sai, chẳng hạn một thiết kế ban đầu phù hợp nhưng dần không còn phù hợp khi dịch vụ phát triển lớn hơn.

Lãi phát sinh dưới những hình thức khó nhận thấy. Chỉ sửa một chức năng nhỏ cũng có thể gây lỗi ở nơi không ngờ tới, còn lập trình viên mới phải mất vài tuần để hiểu những đoạn mã đan xen rối rắm. Thời gian cần cho cùng một công việc cũng tăng dần qua mỗi năm, nhưng vì thay đổi từng ngày rất nhỏ nên khó ai nhận ra.

Điều đáng sợ của tiền lãi là nó phình to như lãi kép. Khi đặt mã mới lên trên mã viết qua loa, mã mới cũng buộc phải được viết gượng ép để phù hợp với cấu trúc thiếu chặt chẽ đó. Cuối cùng, một lối tắt ở một nơi lại tạo ra những đường vòng ở nhiều nơi khác, và phạm vi cần sửa rộng gấp nhiều lần so với ban đầu.

Khi tiền lãi vượt quá tiền gốc, ta sẽ rơi vào tình trạng thời gian ngăn chặn các vấn đề hiện có còn nhiều hơn thời gian xây dựng chức năng mới.

Không phải khoản nợ nào cũng xấu: Các loại nợ kỹ thuật

Bảng trắng trong phòng họp chi chít những ô hộp và mũi tên vẽ tay

Năm 2009, nhà phát triển phần mềm người Anh Martin Fowler đã đề xuất “ma trận bốn góc phần tư về nợ kỹ thuật”, phân loại nợ kỹ thuật theo hai tiêu chí. Một tiêu chí là có biết mình đang mắc nợ hay không, tiêu chí còn lại là có thận trọng hay liều lĩnh khi mắc nợ.

(1) Nợ có chủ đích và thận trọng
Đây là khoản nợ được lựa chọn khi đã biết rõ hệ quả, chẳng hạn: “Hiện tại cần ưu tiên phát hành, cứ làm thế này rồi sửa sau”. Nó có thể là một lựa chọn chiến lược giúp nắm bắt thời điểm thiết yếu đối với hoạt động kinh doanh.

(2) Nợ có chủ đích và liều lĩnh
Đây là trường hợp biết rõ nhưng vẫn làm qua loa với lý do “không có thời gian thiết kế”, và là dạng nguy hiểm nhất.

(3) Nợ mắc phải mà không hay biết
Đây là khoản nợ phát sinh do thiếu kinh nghiệm, hoặc chỉ sau khi hoàn thành công việc mới nhận ra rằng “lẽ ra mình nên làm thế này”. Đó cũng là khoản nợ khó tránh trong quá trình học hỏi.

Điều cần lưu ý là ngay cả khoản nợ có chủ đích cũng nhanh chóng trở thành khoản nợ liều lĩnh nếu không có kế hoạch trả. Những đoạn mã từng được hẹn “sẽ sửa sau” nhưng vẫn giữ nguyên suốt nhiều năm là chuyện rất thường gặp trong thực tế phát triển phần mềm.

Như vậy, nợ kỹ thuật là thứ cần được quản lý bằng cách cân nhắc khi nào nên vay, vay bao nhiêu và trả thế nào, tương tự khoản vay ngân hàng, chứ không phải thứ cần tránh bằng mọi giá.

Cách quản lý và trả nợ kỹ thuật

Tủ máy chủ có dây cáp được sắp xếp gọn gàng và bàn làm việc ngăn nắp với máy tính xách tay

Phương pháp tiêu biểu nhất để trả nợ là tái cấu trúc mã (Refactoring). Tái cấu trúc mã là công việc chỉ chỉnh sửa cấu trúc mã để dễ đọc và dễ sửa hơn, đồng thời giữ nguyên những gì chương trình thực hiện. Vì bề ngoài không có gì thay đổi nên khó thấy được thành quả, do đó điều quan trọng hơn cả là lập trình viên và ban lãnh đạo cùng hiểu chi phí của khoản nợ.

(1) Ghi lại các khoản nợ
Ghi những phần cần sửa sau vào danh sách việc cần làm giúp chúng không bị lãng quên, đồng thời cho phép nhìn ngay ra lượng nợ đã tích lũy.

(2) Đều đặn trả từng chút một
Dành sẵn một phần thời gian phát triển để mỗi lần chỉnh sửa một chút có thể ngăn tiền lãi phình to.

(3) Tạo lưới an toàn bằng kiểm thử
Khi có sẵn các bài kiểm thử tự động, ta có thể kiểm tra ngay xem việc sửa mã có làm hỏng nơi khác hay không, qua đó giảm gánh nặng trả nợ.

(4) Xây dựng lại khi nợ quá lớn
Nếu nợ tích lũy quá nhiều khiến chi phí sửa chữa vượt quá chi phí xây dựng mới, từng bước thay thế hệ thống cũ bằng hệ thống mới cũng là một cách. Tuy nhiên, vì chi phí và rủi ro đều lớn nên cần quyết định thận trọng.


Nợ kỹ thuật dùng khái niệm nợ quen thuộc để thể hiện thực tế của công việc phát triển phần mềm, nơi luôn phải tìm điểm cân bằng giữa tốc độ và chất lượng. Quên mất mình đang mắc nợ còn là vấn đề lớn hơn bản thân việc vay nợ, và khoản nợ được quản lý tốt thậm chí có thể trở thành bàn đạp cho sự tăng trưởng nhanh chóng.

Ghi nhớ rằng thời gian tiết kiệm hôm nay sẽ quay lại thành tiền lãi ngày mai chính là bước đầu tiên để có phần mềm lành mạnh.