Phân loại nợ kỹ thuật (2018)
(technology.riotgames.com)- Riot Games xem nợ kỹ thuật là mã hoặc dữ liệu mà các nhà phát triển trong tương lai sẽ phải trả chi phí, và dựa trên các ví dụ phát triển League of Legends để hệ thống hóa một ngôn ngữ chung nhằm đánh giá nợ
- Tiêu chí đánh giá gồm ba yếu tố: mức độ ảnh hưởng, chi phí sửa chữa và tính lây lan; trong đó tính lây lan đặc biệt chỉ mức độ nợ lan sang các hệ thống, dữ liệu và thực hành phát triển khác theo thời gian
- Các loại nợ được chia thành Local Debt bị giới hạn trong triển khai nội bộ, MacGyver Debt nối tạm hai hệ thống, Foundational Debt với các giả định sâu cắm vào cấu trúc, và Data Debt khi nội dung được xây chồng lên trên khiếm khuyết
- Cataclysm của Jarvan, việc dùng song song std::string và AString, việc BlockBuilder dùng Lua, và lỗi đặt tên block parameter được dùng làm ví dụ cụ thể cho từng loại
- Nợ có tính lây lan thấp có thể vẫn ổn dù để lại lâu, nhưng nợ có tính lây lan cao sẽ khiến chi phí sửa chữa và ảnh hưởng tăng theo thời gian, nên cần sớm cắt đứt đường lan rộng
Ba trục để đánh giá nợ kỹ thuật
- Nợ kỹ thuật là “mã hoặc dữ liệu mà các nhà phát triển trong tương lai sẽ phải trả chi phí”
- Để quyết định có nên sửa một khoản nợ cụ thể ngay bây giờ, sửa sau, hay thực tế là để lại, cần có tiêu chí đo lường chung
- Riot đánh giá nợ kỹ thuật theo ba trục
- impact: ảnh hưởng tới người chơi và nhà phát triển
- fix cost: thời gian cần để sửa và rủi ro khi triển khai
- contagion: nếu để nguyên thì vấn đề lan rộng đến mức nào
-
impact: chi phí lộ ra với người chơi và nhà phát triển
- Với người chơi, nó xuất hiện dưới dạng bug, tính năng bị thiếu, hoặc hành vi ngoài dự kiến
- Với nhà phát triển, nó tích tụ thành chậm trễ khi triển khai, cản trở workflow, và những chi tiết không cần thiết phải ghi nhớ
- Ở đây nhà phát triển không chỉ gồm kỹ sư, mà còn gồm các vai trò tham gia làm game như designer, nghệ sĩ VFX, v.v.
- Một số khoản nợ cản kỹ sư viết mã mới, một số khác cản designer viết script mới hoặc nghệ sĩ VFX tạo particle mới
-
fix cost: thời gian triển khai và rủi ro phát hành
- Chi phí sửa chữa không chỉ gồm thời gian phát triển thực tế, mà còn gồm rủi ro phát sinh khi phát hành bản sửa
- Một lỗi đơn giản trong một hàm đơn lẻ có thể sửa trong vài phút, nhưng một giả định sâu ảnh hưởng tới toàn bộ code line của game có thể mất vài tuần hoặc vài tháng
- Ngay cả một hệ thống “sai” cũng có thể đã được dùng như công cụ để tạo ra một game hay, nên ngay khi sửa, nội dung hiện có có thể bị hỏng
- Ví dụ, thay đổi cách scripting engine xử lý lỗi hoặc cách tính thời điểm tạo particle có thể ảnh hưởng tới hơn 500 kỹ năng của hơn 140 tướng
-
contagion: mức độ lan rộng theo thời gian
- contagion nghĩa là khi để nguyên nợ kỹ thuật, nó lan truyền sang các hệ thống, dữ liệu và cách phát triển khác nhiều đến mức nào
- Sự lan truyền xảy ra qua interface với hệ thống có vấn đề, qua việc sao chép/dán dữ liệu được xây chồng lên nó, và qua thay đổi trong cách triển khai tính năng mới
- Nợ được cô lập tốt thì dù sửa sau, chi phí cũng không khác nhiều so với sửa ngay bây giờ
- Nợ có tính lây lan cao sẽ ngày càng khó sửa theo thời gian, và khi càng nhiều hệ thống bị nhiễm bởi thỏa hiệp cốt lõi, mức độ ảnh hưởng cũng tăng theo
Local Debt: hộp đen chỉ lộn xộn bên trong
- Local Debt giống với mô hình lập trình hộp đen cổ điển
- Nhìn từ bên ngoài, hệ thống hoạt động ổn định, nhưng phần triển khai bên trong có thể rất tệ hoặc rối rắm
- Ví dụ: kỹ năng, tầng mạng, script engine
- Nếu khi phát triển các hệ thống xung quanh không cần để ý đến khoản nợ bên trong, thì tính lây lan tương đối thấp
-
Ẩn dụ đời thực: mắt người
- Về mặt cấu trúc, mắt người tiếp nhận hình ảnh bị đảo ngược, và các dây thần kinh võng mạc tạo điểm mù gần trung tâm mỗi mắt
- Trung tâm thị giác của não đảo dữ liệu lại và lấp điểm mù, để phần còn lại của não tương tác với hình ảnh “đúng”
- Điểm đặc biệt này được khu trú trong hệ thống mắt và dây thần kinh thị giác, còn các hệ thống khác có thể dễ dàng tránh nó, nên trạng thái này là “đủ tốt”
-
Ví dụ trong League: Cataclysm của Jarvan
- Cataclysm của Jarvan cho đến nay vẫn được tạo bằng minion
- Designer có thể dùng công cụ tạo “invisible minion” khi muốn gắn hiệu ứng gameplay vào một vị trí hoặc tập hợp vị trí cụ thể
- RiotXypherous giải thích “minion” được nói tới ở đây trong một bình luận trên Reddit
- Các game object như vậy là một cách ổn định và được hiểu rõ để theo dõi và thực thi logic script
- Tường của Jarvan cần đúng 24 minion để ngăn người chơi thoát ra
- Trước đây là 12, nhưng vì người chơi đôi khi lọt qua giữa các bức tường nên Riot Exgeniar đã tăng lên 24
- Phương án thay thế là cấu trúc ring-terrain, một mảnh logic đơn nhất kiểm soát pathability của Cataclysm; nó có thể làm gọn logic và giảm nhẹ chi phí tính toán
-
Đánh giá Cataclysm
- impact: 1/5
- Việc bức tường được tạo bằng minion hầu như không ảnh hưởng đến các nhà phát triển khác khi tạo nội dung mới
- “Jarvan Ult Hitch” là kết quả của khoản nợ này kết hợp với một bug khi tải cố đọc định nghĩa auto-attack bị thiếu
- fix cost: 2/5
- Hiện chưa thể tạo custom geometry bằng hình dạng tổng hợp nếu không viết mã mới
- Để tạo “area trigger” dạng vòng, cần mã toán học chuyên dụng cho tính toán va chạm vòng
- Riot đang nghiên cứu Constructive Solid Geometry cho mục đích khác, và điều này có thể giảm đáng kể chi phí sửa chữa
- contagion: 1/5
- Khi phát triển tính năng, không cần xét đến cách triển khai tường Jarvan, nên nó được cô lập tốt
- Rủi ro lây lan là khi designer khác sao chép/dán cách triển khai này vào tướng mới, và thực tế điều đó thỉnh thoảng đã xảy ra
- Với tư cách là vấn đề triển khai, khả năng lan rộng tiềm tàng của Cataclysm là thấp và được hiểu rõ
- impact: 1/5
-
Cách xử lý Local Debt
- Đặc trưng điển hình của Local Debt là điểm contagion thấp
- Nếu impact cao hơn fix cost, các nhà phát triển đóng vai “công dân tốt” thường sẽ sửa nó trước khi quá lâu
- Nếu thật sự không có tính lây lan, để lại lâu đến mức cần thiết vẫn an toàn
- Một trong những sai lầm lớn là lao ngay vào Local Debt chỉ vì nó kích thích chủ nghĩa hoàn hảo của kỹ sư nhưng không có ảnh hưởng đủ rộng
- Vì phạm vi thay đổi mang tính cục bộ, việc xác nhận bản sửa và regression test thường dễ dàng
- Các ví dụ đã được sửa gần đây gồm bug liên quan đến inhibitor, Janna’s Monsoon, và Tear of the Goddess
- Bug khiến inhibitor pathing tướng tới tọa độ 0,0,0 trong một số tình huống nhất định
- Vấn đề Janna’s Monsoon bỏ qua spell shield
- Vấn đề Tear of the Goddess cộng dồn khi cast không tốn mana
MacGyver Debt: trạng thái hai hệ thống được dán với nhau bằng băng keo vải
- MacGyver Debt là một loại được đặt tên theo chương trình TV MacGyver giữa thập niên 1980
- Trong ngữ cảnh nợ kỹ thuật, nó chỉ trạng thái hai hệ thống xung đột nhau được “dán bằng băng keo vải” tại các điểm giao diện trên toàn codebase
-
Ẩn dụ đời thực: Seattle
- Seattle từng có hai khu định cư cạnh tranh, mỗi nơi có lưới đường riêng
- Khi hai khu định cư phát triển thành Emerald City ngày nay, các lưới đường hơi khác nhau đã hợp nhất, tạo ra các khu nhà và tòa nhà có hình dạng gượng gạo, cùng việc sử dụng không gian kém hiệu quả
-
Trường hợp của League: std::string và AString
- Trong codebase của League, std::string của C++ và lớp AString tùy chỉnh của Riot cùng tồn tại
- Cả hai đều là cách để lưu trữ, chỉnh sửa và truyền chuỗi
- Riot cho rằng std::string gây ra nhiều cấp phát bộ nhớ “ẩn” và chi phí hiệu năng, đồng thời khiến việc viết code xấu trở nên dễ dàng
- AString được thiết kế với mục tiêu quản lý bộ nhớ cẩn trọng
- Chiến lược thay thế là để hai hệ thống cùng tồn tại và cho phép chuyển đổi qua lại bằng
.c_str()và.Get() - AString được bổ sung các cải tiến để tăng tính dễ dùng, và các kỹ sư được khuyến khích tự thay std::string khi thay đổi code
- Theo cách này, std::string dần dần bị loại bỏ theo từng bước, và giao diện “băng keo vải” giữa hai hệ thống cũng giảm đi cùng với việc dọn dẹp code
-
Đánh giá std::string vs AString
- impact: 2/5
- Phần lớn các cấp phát có tác động cao từ std::string đã được loại bỏ thông qua profiling
- Chi phí chính hiện nay là chi phí chuyển đổi tư duy nhỏ khi chuyển từ hệ thống này sang hệ thống kia
- fix cost: 3/5
- Việc chuyển sang AString không phải là thao tác find-and-replace đơn giản
- AString có các biến thể theo mục đích như AStackString cấp phát ban đầu trên bộ nhớ stack, ARefString cho tham chiếu static string, và AString dựa trên cấp phát heap
- Để thay thế đúng, con người phải trực tiếp xem xét và phán đoán từng điểm; quá trình loại bỏ hệ thống cũ theo từng bước sẽ dài và chậm
- contagion: -2/5
- Bằng cách làm cho AString dễ dùng hơn std::string, contagion đã được đảo theo hướng có lợi
- Mỗi khi kỹ sư check in thay đổi code game, khả năng AString lan rộng hơn lại xuất hiện
- impact: 2/5
-
Cách sửa MacGyver Debt
- Chi phí lớn của MacGyver Debt thường là chi phí trí tuệ khi phải chuyển chế độ mỗi lúc vượt qua ranh giới
- Nếu bị chặn vì bug hoặc tính năng nằm trong hệ thống “wrong”, việc chuyển điểm mục tiêu sang hệ thống “right” thường khá trực tiếp
- Contagion tương đối giữa hệ thống mới và hệ thống hiện có là chỉ số cốt lõi
- Nếu đảo cán cân để phía hệ thống mới có tính lây lan mạnh hơn, hệ thống tốt hơn cuối cùng sẽ thắng
- Cần làm cho hệ thống tốt hơn ở cấp toàn cục cũng hấp dẫn hơn ở cấp cục bộ
- Nếu các kỹ sư bị áp lực thời gian vẫn chọn hướng đến trạng thái cuối mong muốn khi thực hiện tối ưu hóa tham lam trong công việc hằng ngày, thì đó là dấu hiệu đang đi đúng hướng
- Một cách tiếp cận khác là brute-force refactor quy mô lớn; tùy vào mức độ các hệ thống ánh xạ gần nhau đến đâu, có thể sửa một phần hoặc toàn bộ bằng clever regex
Foundational Debt: trạng thái một giả định sâu đã ăn sâu vào toàn bộ cấu trúc
- Foundational Debt là trạng thái trong đó một giả định nào đó ở rất sâu trong hệ thống đã được “nướng” vào toàn bộ cách hệ thống vận hành
- Những người dùng giàu kinh nghiệm của hệ thống có thể khó nhận ra, vì họ xem nó là “vốn dĩ như vậy”
-
Ẩn dụ đời thực: hệ đo lường thông dụng của Hoa Kỳ
- Người lớn lên ở Mỹ phải học thuộc các phép đổi như 1 dặm bằng 5.280 feet, 1 quart bằng 2 pint, 1 gallon bằng 4 quart
- Chính phủ Mỹ đã nhiều lần cân nhắc chuyển sang hệ mét, nhưng vẫn là một trong 7 quốc gia chưa chính thức chấp nhận Système International làm hệ đo lường chính thức
- Món nợ này đã ăn sâu vào biển báo đường bộ, công thức nấu ăn, trường tiểu học và trong đầu mọi người
-
Trường hợp League: BlockBuilder và Lua
- Các ví dụ lớn về Foundational Debt mà Riot từng xử lý gồm Determinism in League of Legends và Game Data Server
- Việc League dùng Lua scripting language cũng là một ví dụ về Foundational Debt
- Designer của League dùng một công cụ tên BlockBuilder để nối các khối chức năng lại với nhau và tạo ra các hành vi phức tạp
- Các khối chức năng bao gồm tính khoảng cách giữa các điểm, tạo minion, xử lý damage, và nhiều loại script flow control
- Tập hợp phép toán mà designer chọn rất đa dạng nhưng có giới hạn, và tham số của từng phép toán cũng bị ràng buộc
- Ở giai đoạn đầu của League of Legends, thay vì lưu các block và tham số ở một định dạng đơn giản, có ràng buộc và phù hợp với dữ liệu, họ quyết định lưu chúng bằng arrays và tables của ngôn ngữ Lua—mạnh mẽ nhưng quá phức tạp cho mục đích này
- Sau đó, khoảng 10 năm phát triển game đã diễn ra trên nền tảng đó, và thao tác với Lua object trở thành một trong những công việc phổ biến nhất trong engine
-
Đánh giá BlockBuilder Lua
- impact: 4/5
- Sự không khớp giữa Lua và không gian bài toán tương ứng gây ra nhiều chi phí
- Trong mỗi frame của logic BlockBuilder, callstack bị “nhiễm bẩn” bởi khoảng 6 marshalling stack frame
- Công việc marshalling không hề rẻ xét theo mức sử dụng CPU của server
- Việc đọc diff của các thay đổi script trở nên khó một cách không cần thiết
- Để parsing/searching script file nhằm hiểu chức năng, cần hiểu khá sâu về ngôn ngữ Lua
- fix cost: 4/5
- Lua đã cắm sâu vào engine nên rất khó loại bỏ
- Một đề xuất hiện tại là tạo một wrapper class hoạt động giống Lua object nhưng bên trong là một struct đơn giản hơn nhiều, rồi từ từ chuyển phần bên trong scripting sang một dạng phù hợp hơn
- Dù chọn cách tiếp cận nào, việc này cũng phải được thực hiện thận trọng và suy xét kỹ
- contagion: 4/5
- Mỗi khi một hệ thống tiếp xúc với scripting, hệ thống đó lại bị định hình bởi các thao tác và yêu cầu của Lua backend
- Scripting là đơn vị logic cốt lõi của LoL
- Riot thêm một Building Block mới trung bình khoảng 3–4 ngày một lần, và mỗi Building Block đều thao tác trực tiếp với Lua object
- Càng trì hoãn việc thay thế Lua, việc thay thế Lua sẽ càng khó hơn
- impact: 4/5
-
Cách giảm Foundational Debt
- Foundational Debt có xu hướng đạt điểm cao trên cả ba trục impact, fix cost và contagion
- fix cost cao khiến người ta tiếp tục dùng một hệ thống không hoàn hảo, và đôi khi đó cũng là lựa chọn đúng
- Tuy nhiên, do impact và contagion đều cao, việc sửa một Foundational Debt nghiêm trọng có thể mang lại phần thưởng lớn
- Chiến lược sửa phổ biến nhất mà Riot quan sát được là dựng một hệ thống mới bên cạnh hệ thống hiện có
- Nếu có thể, hãy biến foundational debt hiện tại thành MacGyver Debt, rồi dùng conversion operation để chuyển qua lại giữa hệ thống mới và hệ thống cũ trong khi port dần dần
- Cách này giúp bắt đầu thu được lợi ích trong một số khu vực nhất định, đồng thời hạn chế mức độ phơi nhiễm rủi ro
- Khi không thể chuyển đổi theo cách đó, có thể tạo compile time switch, hoặc nếu được thì loading time switch, để xây dựng niềm tin vào hệ thống mới
- Cách dùng compile time switch đang được áp dụng trong chuyển đổi GDS
- Cách dùng loading time switch đã phát huy hiệu quả trong Determinism
Data Debt: trạng thái nội dung khổng lồ chất chồng lên trên các khiếm khuyết
- Data Debt xảy ra khi một lượng lớn nội dung được xây dựng chồng lên trên các loại nợ kỹ thuật khác
- Điểm khởi đầu có thể là bug trong scripting system, file format không phù hợp với item, hoặc hai hệ thống không khớp tốt với nhau
- Khi một lượng lớn nội dung như art, scripts, sounds được tạo ra trên khiếm khuyết code đó, việc sửa nợ kỹ thuật ban đầu trở nên rất rủi ro
- Theo thời gian, việc xác định thứ gì sẽ bị hỏng trở nên khó khăn đến mức đau đầu
-
Ví von trong thế giới thực: DNA
- Genome của sinh vật được tích lũy chậm rãi qua hàng triệu năm thông qua mutation, transcription error và evolutionary pressure
- Một số lỗi sao chép là vô dụng nhưng không gây hại, một số gây hại, và một số mang lại lợi thế mạnh mẽ
- Rất khó để tìm ra một đoạn DNA thực sự làm gì
- Chúng ta đã hiểu hoàn toàn base pair có ý nghĩa gì, và cách các tập hợp base pair được dịch thành amino acid để protein construction
- Chúng ta cũng đã bắt đầu hiểu thêm về một số non-encoding role của DNA
- Nhưng trong hơn 3 tỷ base pair của human genome, vẫn còn nhiều phần mà chúng ta hầu như chưa hiểu
- Tập CRISPR của Radiolab nói về một câu đố như vậy mới được giải gần đây
-
Trường hợp ở League: block parameter naming bug
- Data Debt của League of Legends có tác động lớn nhất khi nó biến một chỉnh sửa vốn nhỏ nhặt thành công việc cực nhọc
- Các game engineer tích lũy hiểu biết sâu về cách triển khai hệ thống game, và trở nên thành thạo trong việc dự đoán thay đổi code nào sẽ làm hỏng dữ liệu nào
- Data Debt là một trong những cân nhắc quan trọng nhất khi thay đổi LoL engine
- Một trường hợp Data Debt đã được sửa vài năm trước là bug liên quan đến block parameter trong BlockBuilder scripting language
- Trong toy example, nếu cố tăng armor của Owner bằng một biến và một hằng số, giá trị kỳ vọng là 25 bonus armor, gồm biến Delta 20 cộng với hằng số 5
- Nếu tên biến trùng với tên parameter, trước đây kết quả sẽ là 40
- Tác giả nói rằng chính họ cũng không biết vì sao không phải là 45
-
Quá trình sửa thực tế
- Khi NoopMoney, engineer của Champions team, cố sửa hành vi này, thay đổi code thực tế chỉ là xóa 4 dòng
- Tuy nhiên, khoản nợ có tính lây lan cao này đòi hỏi phải lập kế hoạch kỹ lưỡng ngay cả với một thay đổi nhỏ
- Bất kỳ numerical parameter nào trong 400.000 dòng script của LoL cũng có thể đang bị nhân đôi bởi bug này
- Vấn đề lớn hơn là game đã được balance và tuning theo các giá trị có khả năng bị nhân đôi đó, nên các script ấy đang hoạt động “correctly”
- NoopMoney phải làm cho fix có thể toggle trên Live để phòng các bug ngoài dự kiến
- Để xác định script nào phụ thuộc vào bug này, họ thực hiện regex searching rộng khắp và QA sweep
- Cuối cùng, các vấn đề do bản sửa gây ra tương đối nhỏ, và chỉ một số ít champion script cần thay đổi
- Vì Data Debt, việc dự đoán kết quả rất khó
-
Đánh giá Parameter Naming Bug
- impact: 2/5
- Tác động khi xảy ra là nhỏ
- Nó có thể nhân đôi giá trị được truyền vào và loại bỏ hằng số
- Nó trở thành một mẩu tribal knowledge vô dụng nữa mà các designer và engineer biết đến phải ghi nhớ
- developer mindshare là tài nguyên có giá trị, không đáng bị lãng phí như vậy
- fix cost: 2/5
- Nhìn chung, bản thân việc sửa khá trực tiếp
- Có thể tạo live feature toggle để tăng mức độ chắc chắn về độ an toàn của fix
- Phần tốn kém nhất là screening ban đầu để đánh giá phạm vi vấn đề nhằm xác định mục tiêu kiểm thử
- contagion: 4/5
- Điều không may là bug này nhắm vào một hành vi rất hợp lý
- Nếu muốn gây damage cho unit, việc lưu giá trị vào biến tên “Damage” là hoàn toàn hợp lý
- Bug được kích hoạt khi ApplyDamage block nhận amount qua parameter cùng tên
- Nếu người khác copy/paste block đó để tạo spell tương tự, bug sẽ lan rộng hơn
- impact: 2/5
-
Vì sao Data Debt có tính lây lan cao
- Data Debt thường được đánh giá là có chi phí sửa cao vì nó khiến việc đánh giá tác động của thay đổi trở nên khó khăn
- Điều đáng lo hơn là do bản chất của data, nó gần như luôn có tính lây lan rất cao
- Việc copy/paste data hiện có để tạo data mới thường được chấp nhận
- Khi tạo một skillshot spell mới, bắt đầu từ Ezreal’s Mystic Shot có thể tiết kiệm rất nhiều thời gian, và các vấn đề trong data hiện có cũng lan sang data hậu duệ
- Vì data hiếm khi được review kỹ thuật tương tự code review, nên ngay cả khi bad practice đã được nhiều người biết đến, vẫn khó nhận ra và ngăn sự lan rộng của nó
- Để sửa vấn đề trong data, thường cần người có mắt và não trực tiếp xác minh; chỉ compiler và formal logic là không đủ
-
Hai cách tiếp cận để sửa Data Debt
- Cách tiếp cận thứ nhất là do it right checkbox
- Đây là cách tạo toggle cho data creator giữa hành vi “broken” hiện có và hành vi “fixed” mới
- Lý tưởng nhất là đặt fixed version làm mặc định, còn old content dùng broken version
- Sau đó có thể chuyển dần sang phiên bản mới bằng replacement chậm mà chắc, giống MacGyver Debt
- Nhược điểm là phát sinh chi phí vĩnh viễn khi ngày càng thêm nhiều yếu tố không cần thiết vào editing UI
- Cách tiếp cận thứ hai là just fix the damn thing
- Đây là cách NoopMoney đã dùng cho parameter naming bug
- Sau khi sửa bug, cố gắng sửa tất cả data bị ảnh hưởng đáng kể
- Các kỹ thuật để làm bớt đáng sợ gồm nhiều grep và regex searching để hiểu theoretical impact, targeted testing, và chuẩn bị toggle để quay về hành vi cũ nếu phát hiện thiếu sót nghiêm trọng hơn sau khi ship
- Determinism giúp ích nhiều cho việc kiểm thử các thay đổi như vậy bằng cách cho phép xác nhận server trước và sau thay đổi có tạo cùng kết quả hay không
- Cách tiếp cận thứ nhất là do it right checkbox
Tổng kết: phải đưa tính lây lan vào đánh giá chi phí
- Các chỉ số đo lường nợ kỹ thuật là impact, fix cost và contagion
- impact là ảnh hưởng lên khách hàng và developer
- fix cost là thời gian và rủi ro
- contagion là mức độ lan truyền của vấn đề
- Hầu hết developer thường xuyên cân nhắc impact và fix cost, nhưng thảo luận về contagion tương đối hiếm
- contagion có thể trở thành kẻ thù tệ nhất của developer khi vấn đề ăn sâu và ngày càng khó loại bỏ
- Ngược lại, nếu làm cho fix có tính lây lan mạnh hơn vấn đề, có thể biến contagion thành vũ khí
- Hầu hết nợ kỹ thuật thấy ở League có thể được xếp vào một trong bốn loại
- Local Debt: khoản nợ giống một black box có bên trong lộn xộn
- MacGyver Debt: khoản nợ trong đó 2 hoặc nhiều hệ thống được dán với nhau như bằng băng keo nhờ conversion function
- Foundational Debt: khoản nợ mà toàn bộ cấu trúc được xây dựng trên một giả định không may
- Data Debt: khoản nợ trong đó khối lượng data khổng lồ chất chồng lên các loại nợ khác, khiến việc sửa chữa rủi ro và tốn thời gian
1 bình luận
Ý kiến trên Hacker News
Tính lây lan tự nó là lý do vì sao interface là một trong những yếu tố quan trọng nhất của thiết kế và cần được cân nhắc kỹ
Nếu có một interface đẹp nhưng phần triển khai chưa tối ưu thì khi có thời gian có thể dọn dẹp lại khá dễ, còn chiều ngược lại thì gần như không đúng
Đôi khi thiếu cả hai
Trong những trường hợp như vậy, có thể ngăn việc lây lan trở nên quá nghiêm trọng bằng cách cưỡng chế theo kiểu tổ hợp các module nhỏ và trách nhiệm đơn lẻ. Không cần quá nhiều hiểu biết về tương lai hay quá nhiều thời gian, chỉ cần tránh interface bề mặt rộng kiểu búp bê Nga, nơi nhiều biến thể hành vi được điều khiển bằng tham số. Nên đẩy cấu hình, parsing và quyết định hành vi ra rìa của logic, thay vì để chúng ngấm vào toàn bộ mô hình bên dưới
Ngay cả khi chủ động bắt đầu với quyết tâm tránh technical debt bằng mọi giá thì làm đúng vẫn rất khó, và nó đòi hỏi nhiều hơn là chỉ sự tự tin kỹ thuật hay tầm nhìn kiến trúc. Trên thực tế, nó đi vào địa hạt của dự đoán tương lai
Nếu vô tình nướng một cách triển khai ngớ ngẩn vào interface, thì thường sẽ không thể sửa chỉ bằng cách thay đổi triển khai nữa. Ví dụ hiện ra ngay là hành vi sorting và pagination. Các lập trình viên junior, và giờ đây cả nhiều senior lẽ ra nên biết rõ hơn, thường bắt đầu từ những request dùng tham số kiểu
limit/offset, và điều đó dẫn tới các vấn đề hiệu năng khủng khiếp cùng hành vi kỳ quặc. Cách pagination hoạt động hiệu quả và các tùy chọn sorting có thể hỗ trợ tốt về hiệu năng vốn gắn chặt với bản chất dữ liệu và lựa chọn data store. Ai chưa từng trải qua quá trình này ở tầng thấp hơn thì khó có khả năng tạo ra đúng interface ở tầng trên nếu chưa đào đủ sâu vào phần triển khai trướcTrong đa số trường hợp tôi không muốn nhìn vào triển khai, tôi chỉ muốn xem interface được tài liệu hóa tử tế. Nếu không thể mô tả hành vi của interface bằng những lời đơn giản thì có gì đó đang sai
QWERTY nổi tiếng là một physical interface không tối ưu, và vô lăng cũng có thể là ví dụ tương tự. Bên phía máy tính, x86 là ví dụ tiêu biểu của một interface bề ngoài không tối ưu
Khá ngạc nhiên khi bài này được viết bởi một engineering manager
Trong số các manager tôi từng làm việc cùng, chưa ai có thể nói về codebase của chúng tôi ở mức chi tiết kỹ thuật như thế này. Ngay cả những người từng là kỹ sư trước đó cũng vậy
Tuy vậy, nói công bằng thì chưa có manager nào được thăng chức nội bộ, và người trong công ty, bao gồm cả tôi, lại không muốn bỏ làm engineering, nên có một thói quen xấu là tuyển manager từ bên ngoài
Có vẻ như loại nợ tôi thấy phổ biến nhất đã bị bỏ sót: nợ của founder
Đó là loại nợ do các founder tạo ra để tung ra công nghệ nhanh và có giá trị. Những gì trông như trái chín dễ hái lại trở thành nền móng của cả hệ thống
Nhiều văn kiện lập quốc của nhiều quốc gia cũng thuộc nhóm này lol (nhưng không phải kiểu USA! USA! USA!)
Nợ MacGyver và nợ nền tảng là hai loại gần nhất, nhưng cả hai đều không chỉ ra hiện tượng này một cách chính xác
Đây là một bài viết rất hay xét từ góc độ kỹ thuật
Tuy vậy, tôi thấy đây giống danh pháp hơn là “taxonomy”, vì nó không cố ý bao quát đầy đủ và cũng không loại trừ lẫn nhau, dù có thể tôi sai. Các ví dụ vật lý cho từng mục đặc biệt hay và gợi nhiều suy nghĩ
Như thường lệ, có một điểm hơi vướng về mặt triết học. “Ba trục” ở phần đầu có vẻ như là lợi nhuận và đầu tư của RoI truyền thống, cộng thêm một tiểu loại lợi nhuận có tính hướng tới tương lai và có điều kiện. Tôi đoán quyết định này trên thực tế vẫn hoạt động tốt, và thực hành phát triển video game không nhất thiết phải tuyệt đối khoa học, nhưng có thêm một chút chặt chẽ về mặt triết học cũng không hại gì
Khi đó cũng đã có thảo luận:
A Taxonomy of Technical Debt - https://news.ycombinator.com/item?id=16810092 - tháng 4 năm 2018 (113 bình luận)
Và còn cái này nữa:
A Taxonomy of Tech Debt (2018) - https://news.ycombinator.com/item?id=39782923 - tháng 3 năm 2024 (1 bình luận)
Cách giải thích “định nghĩa technical debt là code hoặc dữ liệu sẽ khiến các lập trình viên tương lai phải trả chi phí” gần như là cách hay nhất tôi từng thấy
Giống như mọi loại nợ, khi gánh nợ cần áp dụng một ngưỡng để cân bằng giữa nhu cầu tức thời và chi phí tương lai. Tôi có cảm giác đa số mọi người, không chỉ riêng lập trình viên, đều đánh giá quá cao nhu cầu trước mắt và đánh giá quá thấp chi phí tương lai
Cá nhân tôi gần như ghét mọi loại nợ một cách bệnh lý. Có khi tôi sẵn sàng bỏ thêm một ngày để tách riêng thứ gì đó có thể hữu ích trong tương lai. Tỷ lệ đúng có lẽ chỉ khoảng 50%, nhưng mỗi lần làm kiểu đó thì thói quen lại được củng cố và luồng công việc mặc định cũng nhanh hơn
Đến giờ tôi đã làm ở 3 “startup”, và đều gia nhập sau khi họ đã có doanh thu đủ để trả mức lương gần sát mặt bằng chuẩn
Điều tôi thấy thường xuyên nhất là nhiều founder hiểu lẫn lộn một cách mơ hồ giữa ý tưởng họ nghĩ ra, thứ thực sự được xây dựng, và phần đã được triển khai mà thực tế còn chạy được
Từ sau khi đọc bài này lần đầu, tôi đã dùng từ tính lây lan để giải thích technical debt, và nó khá khớp
Tôi không chắc “nợ cục bộ” có thể được gọi là technical debt trong tình huống thông thường hay không
Trên thực tế lúc nào đó cũng sẽ có những chỗ bẩn, và việc đóng gói chúng rồi giấu đi để không ai bị ảnh hưởng là chuyện bình thường. Trừ khi yêu cầu thay đổi thì gần như không cần chạm vào, và nếu có thay đổi mà dù là triển khai nào cũng phải sửa thì như vậy là ổn
Nếu ví dụ 24 instance minion không chỉ đơn giản là thiếu thanh lịch mà là vấn đề thực sự, thì nó có vẻ gần với nợ nền tảng hơn, ở chỗ “minion” đã trở thành đơn vị cơ bản đơn giản nhất trong khi có thể đã có một thứ nhẹ hơn
Nếu khuyến khích lập trình viên chỉnh sửa cả những module cũ, đã trưởng thành và không cần đụng tới, thì đây là cách tốt để khiến nó không tích tụ tới mức thành vấn đề
Một khía cạnh quan trọng là có những lúc người ta cố ý gánh technical debt để đổi lấy lợi ích ngắn hạn
Khi đó lợi ích ấy cũng trở thành một trục khác cần được cân nhắc trên bàn cân
Muốn xây một tòa nhà mới và hoàn thành công việc ngay bây giờ thay vì chờ đến 15 năm nữa khi có vốn? Thì đi vay
Nợ là một công cụ, nhưng là công cụ mạnh và nguy hiểm. Nếu không thừa nhận mình đang dùng nó và không tôn trọng nó thì sẽ bị thương. Hoặc ai đó nhận quả lựu đạn từ tay bạn sẽ bị thương. Cũng như nợ thật