Các quy luật của kỹ nghệ phần mềm
(lawsofsoftwareengineering.com)- Một bộ sưu tập tập hợp 56 nguyên tắc và mô hình ảnh hưởng đến hệ thống phần mềm, đội ngũ và việc ra quyết định, bao quát nhiều lĩnh vực từ vận hành đội nhóm đến kiến trúc, chất lượng, thiết kế và ra quyết định
- Các quy luật liên quan đến đội nhóm như Định luật Conway, định luật Brooks, số Dunbar cho thấy cấu trúc tổ chức tác động trực tiếp đến thiết kế hệ thống và năng suất
- Ở mảng kiến trúc, tài liệu tổng hợp các ràng buộc và nguyên tắc bắt buộc phải cân nhắc khi thiết kế hệ thống phức tạp như Định luật Hyrum, định lý CAP, định luật Gall
- Các quy luật liên quan đến chất lượng đề cập đến nợ kỹ thuật, tháp kiểm thử, định luật Kernighan cùng những khó khăn thực tế trong việc duy trì chất lượng mã và gỡ lỗi
- Ở mảng ra quyết định, nội dung bao quát các thiên kiến nhận thức và tiêu chí phán đoán mà quá trình phát triển dễ mắc phải như hiệu ứng Dunning-Kruger, ngụy biện chi phí chìm, nguyên lý Pareto
Đội nhóm (Teams)
1. Định luật Conway (Conway's Law)
Tổ chức sẽ thiết kế những hệ thống phản chiếu chính cấu trúc giao tiếp của mình
- Kiến trúc phần mềm có xu hướng tự nhiên đi theo cấu trúc giao tiếp của tổ chức đã tạo ra nó
- Nếu đội được chia thành 3 nhóm thì hệ thống cũng có xu hướng được chia thành 3 mô-đun lớn
- Cũng có "chiến lược Conway ngược" (Inverse Conway Maneuver): cách tiếp cận tái cấu trúc đội ngũ trước để phù hợp với kiến trúc mong muốn
- Khi áp dụng microservices, việc đồng bộ ranh giới đội ngũ và ranh giới dịch vụ sẽ hiệu quả hơn
2. Định luật Brooks (Brooks's Law)
Thêm nhân lực vào một dự án phần mềm đang chậm tiến độ sẽ chỉ khiến nó còn chậm hơn
- Khi người mới tham gia, các thành viên hiện có phải tốn thời gian cho đào tạo và điều phối, khiến năng suất tổng thể tạm thời giảm
- Khi số thành viên tăng, số tuyến giao tiếp tăng theo cấp số nhân (với n người là n(n-1)/2)
- Frederick Brooks đã khái quát điều này trong cuốn The Mythical Man-Month năm 1975, dựa trên kinh nghiệm từ dự án IBM OS/360
- Có thể giải thích định lượng bằng Định luật Little (L = λ × W): khi thêm người, WIP (công việc đang làm) tăng nhưng throughput đình trệ, làm lead time thậm chí còn tăng
- Giải pháp không phải là thêm người mà là điều chỉnh phạm vi hoặc thay đổi lịch trình
3. Số Dunbar (Dunbar's Number)
Giới hạn nhận thức về số lượng mối quan hệ mà một người có thể duy trì ổn định là khoảng 150 người
- Con số do Robin Dunbar đưa ra từ mối tương quan giữa kích thước não linh trưởng và quy mô nhóm xã hội
- Cấu trúc phân tầng xã hội của Dunbar: ~5 người (quan hệ thân thiết), ~15 người (cộng sự đáng tin cậy), ~50 người (quan hệ công việc gần gũi), ~150 người (kết nối xã hội ổn định)
- Khi tổ chức kỹ thuật vượt quá 150 người, giao tiếp phi chính thức chạm tới giới hạn nên cần cấp bậc và quy trình chính thức
- "Two-Pizza Team" của Amazon (5~10 người) phản ánh rằng ngay cả trong phạm vi 150 người, hợp tác thực chất vẫn diễn ra ở những đơn vị nhỏ hơn
4. Hiệu ứng Ringelmann (The Ringelmann Effect)
Quy mô nhóm càng lớn thì năng suất cá nhân càng giảm
- Khi nhóm có nhiều thành viên hơn, mức đóng góp của mỗi cá nhân giảm xuống, tạo ra hiện tượng "ỷ lại tập thể" (social loafing)
- Trong đội phần mềm, khi quy mô đội tăng thì cảm giác trách nhiệm cá nhân bị pha loãng và chi phí điều phối tăng lên
- Giải thích vì sao các đội nhỏ thường tạo ra sản lượng trên đầu người cao hơn
5. Định luật Price (Price's Law)
Số người tương ứng với căn bậc hai của tổng số người tham gia sẽ thực hiện 50% tổng khối lượng công việc
- Trong một tổ chức 100 người, khoảng 10 người đảm nhận một nửa khối lượng công việc
- Tổ chức càng lớn thì mức độ phụ thuộc vào một số ít cá nhân hiệu suất cao càng sâu sắc
- Giải thích vì sao khi mở rộng đội, năng suất không tăng tuyến tính
6. Định luật Putt (Putt's Law)
Người hiểu công nghệ thì không quản lý, còn người quản lý thì không hiểu công nghệ
- Một cách diễn đạt châm biếm về khoảng cách giữa vai trò quản lý và chuyên môn kỹ thuật trong tổ chức công nghệ
- Khi thiết kế cấu trúc lãnh đạo kỹ thuật, cần nhận thức khoảng cách này và có cơ chế bù đắp
7. Nguyên lý Peter (Peter Principle)
Trong tổ chức, mọi nhân viên có xu hướng được thăng tiến đến mức bất tài của chính mình
- Mô tả kiểu mẫu một người giỏi ở vai trò hiện tại được thăng chức và trở nên kém năng lực trong vai trò mới
- Phản ánh thực tế rằng một lập trình viên giỏi chưa chắc đã trở thành quản lý giỏi
- Cho thấy sự cần thiết của mô hình dual ladder tách riêng lộ trình IC (Individual Contributor) và lộ trình quản lý
8. Bus Factor
Số lượng tối thiểu thành viên rời đi có thể khiến dự án rơi vào rủi ro nghiêm trọng
- Nếu Bus Factor bằng 1 thì đó là trạng thái có điểm lỗi đơn (Single Point of Failure)
- Việc nâng Bus Factor thông qua chia sẻ tri thức, pair programming và tài liệu hóa là rất quan trọng
- Code review và đào tạo chéo là những cách thực tế để cải thiện Bus Factor
9. Nguyên lý Dilbert (Dilbert Principle)
Doanh nghiệp có xu hướng thăng chức những nhân viên kém năng lực lên vị trí quản lý để hạn chế thiệt hại
- Một quan sát mang tính châm biếm do Scott Adams đề xuất, là biến thể của Nguyên lý Peter
- Một nghịch lý trong tổ chức khi vị trí quản lý bị xem là nơi gây ít thiệt hại nhất cho công việc thực thi
Lập kế hoạch (Planning)
10. Tối ưu hóa sớm / Nguyên lý tối ưu hóa của Knuth (Premature Optimization)
Tối ưu hóa sớm là gốc rễ của mọi điều xấu
- Donald Knuth nêu ra trong bài báo năm 1974: "Trong khoảng 97% trường hợp, nên bỏ qua những tối ưu nhỏ về hiệu năng"
- Vì khoảng 20% mã chiếm 80% thời gian thực thi, nên tối ưu phần 80% còn lại thường là lãng phí
- Trình tự đúng là: trước hết làm cho nó chạy được → làm cho nó đúng → nếu cần thì mới làm cho nó nhanh
- Mã đã tối ưu thường phức tạp hơn, vì vậy cần dùng profiling để xác định điểm nghẽn thực sự rồi mới tối ưu
11. Định luật Parkinson (Parkinson's Law)
Công việc sẽ nở ra cho đến khi lấp đầy toàn bộ thời gian được giao
- Nếu deadline là 2 tuần thì công việc kéo dài 2 tuần, nếu là 4 tuần thì nó kéo dài 4 tuần
- Đây là lý do trong dự án phần mềm, việc đặt mốc tiến độ ngắn và rõ ràng rất quan trọng
- Agile dựa trên sprint là một cách ứng phó thực tế với quy luật này
12. Quy tắc 90-90 (The Ninety-Ninety Rule)
90% đầu tiên của mã chiếm 90% thời gian phát triển, còn 10% còn lại lại chiếm thêm 90% thời gian nữa
- Cảnh báo rằng 10% cuối của dự án phần mềm (edge case, polishing, sửa lỗi) mất lâu hơn dự kiến rất nhiều
- Câu nói "gần hoàn thành" trên thực tế có thể mới chỉ ở nửa chặng đường của toàn bộ lịch trình
13. Định luật Hofstadter (Hofstadter's Law)
Dù đã tính đến Định luật Hofstadter, mọi thứ vẫn luôn mất nhiều thời gian hơn dự kiến
- Một quy luật có cấu trúc tự quy chiếu đệ quy, diễn tả bản chất khó khăn cố hữu của việc ước lượng lịch trình phần mềm
- Thực tế là dù có thêm buffer thì lịch vẫn tiếp tục trễ
- Douglas Hofstadter giới thiệu điều này trong cuốn Gödel, Escher, Bach năm 1979
14. Định luật Goodhart (Goodhart's Law)
Khi một chỉ số đo lường trở thành mục tiêu, nó sẽ không còn là chỉ số đo lường tốt nữa
- Một ví dụ điển hình là khi đặt code coverage làm KPI, đội ngũ sẽ tạo ra nhiều bài test vô nghĩa
- Nếu đo năng suất bằng số dòng mã (LOC), kết quả là mã dài dòng không cần thiết sẽ được tạo ra
- Cần tập trung vào việc tạo ra giá trị cốt lõi thay vì tối ưu theo chỉ số
15. Định luật Gilb (Gilb's Law)
Với những gì cần được định lượng, đo lường theo cách nào đó vẫn tốt hơn là không đo lường
- Dù không thể đo lường hoàn hảo, đo lường tương đối vẫn luôn hữu ích hơn là không đo
- Áp dụng được cả với những hạng mục khó định lượng như chất lượng phần mềm hay mức độ hài lòng của người dùng
Kiến trúc (Architecture)
16. Định luật Hyrum (Hyrum's Law)
Khi có đủ nhiều người dùng API, sẽ luôn có ai đó phụ thuộc vào mọi hành vi có thể quan sát được của hệ thống
- Không chỉ đặc tả API chính thức, mà cả các hành vi không chính thức như timing, định dạng thông báo lỗi, thứ tự sắp xếp cũng trở thành đối tượng bị phụ thuộc
- Trường hợp Microsoft Windows trong quá khứ đã duy trì hành vi của phiên bản cũ để đảm bảo tương thích với các ứng dụng bên thứ ba phụ thuộc vào hành vi không được tài liệu hóa và lỗi
- Được Hyrum Wright của Google quan sát từ trải nghiệm thay đổi thư viện nội bộ của Google vào khoảng 2011-2012
- Đồng nghiệp Titus Winters đã đặt tên là "Hyrum's Law" (được đưa vào Software Engineering at Google)
- Hợp đồng thực tế không phải là API chính thức mà là toàn bộ hành vi được quan sát trên thực tế
17. Định luật Gall (Gall's Law)
Một hệ thống phức tạp hoạt động được nhất định là kết quả tiến hóa từ một hệ thống đơn giản hoạt động được
- Nếu thiết kế một hệ thống phức tạp ngay từ đầu thì sẽ có quá nhiều biến số chưa được kiểm chứng, nên xác suất thất bại cao
- Cơ sở lý thuyết cho cách tiếp cận MVP (Minimum Viable Product)
- Trường hợp Facebook bắt đầu từ một hệ thống hồ sơ đơn giản dành cho sinh viên Harvard vào năm 2004 rồi mở rộng dần từng bước
- Ngay cả khi chuyển đổi sang microservices, việc bắt đầu từ monolith rồi tách dần từng bước cũng có lợi
- Được John Gall đề xuất trong cuốn Systemantics năm 1975 (một tác phẩm kinh điển cult được xuất bản sau khi bị 30 nhà xuất bản từ chối)
18. Định luật trừu tượng rò rỉ (The Law of Leaky Abstractions)
Mọi trừu tượng không tầm thường (non-trivial) đều bị rò rỉ ở một mức độ nào đó
- ORM che giấu SQL, nhưng khi phát sinh vấn đề hiệu năng thì cuối cùng vẫn phải kiểm tra các query được tạo ra, đây là ví dụ tiêu biểu
- Garbage collection của Java/Python cũng là một dạng trừu tượng, nhưng các hành vi nội bộ như pause của GC vẫn ảnh hưởng đến hiệu năng
- Bài học không phải là bản thân trừu tượng là xấu, mà là phải chuẩn bị cho lúc trừu tượng bị phá vỡ
- Joel Spolsky đã giới thiệu điều này trong một bài blog năm 2002 cùng với các ví dụ như TCP, bộ nhớ ảo
- Cũng có liên hệ về mặt ngữ cảnh với câu nói của George Box: "Mọi mô hình đều sai, nhưng một số mô hình vẫn hữu ích"
19. Định luật Tesler / Định luật bảo toàn độ phức tạp (Tesler's Law)
Mọi ứng dụng đều có một độ phức tạp nội tại không thể loại bỏ; chỉ có thể chuyển nó đi chứ không thể xóa bỏ
- Câu hỏi cốt lõi: ai sẽ gánh độ phức tạp (người dùng vs hệ thống)
- Calendly hấp thụ độ phức tạp của việc điều phối lịch vào hệ thống, còn chuỗi email thì đẩy nó sang cho người dùng
- Thiết kế tốt sẽ chuyển độ phức tạp từ trải nghiệm người dùng vào bên trong hệ thống
- Được Larry Tesler xác lập vào thập niên 1980 trong quá trình làm việc với Apple Lisa và GUI đời đầu
20. Định lý CAP (CAP Theorem)
Hệ thống phân tán chỉ có thể đảm bảo hai trong ba yếu tố: nhất quán (C), sẵn sàng (A), khả năng chịu phân hoạch (P)
- Phân hoạch mạng là điều không thể tránh khỏi trong thực tế, nên lựa chọn thực chất là nhất quán vs sẵn sàng
- Hệ thống CP (ví dụ: MongoDB): khi xảy ra phân hoạch sẽ chặn ghi để duy trì đồng bộ giữa mọi replica
- Hệ thống AP (ví dụ: Cassandra, DNS): vẫn duy trì phản hồi yêu cầu trong lúc phân hoạch, chấp nhận sự không nhất quán tạm thời giữa các replica
- Được Eric Brewer đề xuất năm 2000 trong bối cảnh web service, và được Gilbert & Lynch chứng minh chính thức vào năm 2002
21. Hiệu ứng hệ thống thứ hai (Second-System Effect)
Sau một hệ thống nhỏ nhưng thành công, thường sẽ xuất hiện một hệ thống kế tiếp cồng kềnh và bị thiết kế quá mức
- Mô thức đổ mọi ý tưởng vào hệ thống thứ hai vì có thêm sự tự tin nhờ thành công của hệ thống đầu tiên
- Feature creep và over-generalization là các nguyên nhân chính
- Được Frederick Brooks nhận diện trong The Mythical Man-Month
22. Những ngộ nhận của điện toán phân tán (Fallacies of Distributed Computing)
8 giả định sai lầm mà những người mới thiết kế hệ thống phân tán thường hay mắc phải
- 8 ngộ nhận: (1) mạng là đáng tin cậy, (2) độ trễ bằng 0, (3) băng thông là vô hạn, (4) mạng là an toàn, (5) topology không thay đổi, (6) chỉ có một quản trị viên, (7) chi phí truyền tải bằng 0, (8) mạng là đồng nhất
- Nếu thiết kế dựa trên các giả định này, trong môi trường production sẽ phát sinh sự cố ngoài dự kiến và vấn đề hiệu năng
23. Định luật hệ quả ngoài ý muốn (Law of Unintended Consequences)
Khi thay đổi một hệ thống phức tạp, phải dự liệu các kết quả ngoài dự kiến
- Khi thay đổi một component của hệ thống, tác dụng phụ có thể phát sinh ở những nơi không thể dự đoán trước
- Nguyên tắc này củng cố sự cần thiết của chaos engineering và kiểm thử toàn diện
24. Định luật Zawinski (Zawinski's Law)
Mọi chương trình đều cố gắng mở rộng cho đến khi có thể đọc email
- Châm biếm hiện tượng phình to tính năng (feature bloat), khi phần mềm thành công rồi liên tục muốn thêm ngày càng nhiều chức năng
- Do Jamie Zawinski (một lập trình viên đời đầu của Netscape) quan sát
- Lời cảnh báo về xu hướng một công cụ đơn giản theo thời gian lại muốn trở thành một nền tảng đa năng
Chất lượng (Quality)
25. Quy tắc Boy Scout (The Boy Scout Rule)
Hãy để lại code ở trạng thái tốt hơn so với lúc bạn tìm thấy nó
- Trọng tâm không phải là refactor quy mô lớn mà là cải tiến liên tục và từng bước
- Thực hành mỗi lần một cải tiến nhỏ như sửa tên hàm khó hiểu, loại bỏ code trùng lặp, bổ sung test còn thiếu
- Robert C. Martin (Uncle Bob) đã áp dụng vào phát triển phần mềm trong Clean Code (2008)
- Nguyên tắc của các kỹ sư Google "If you touch it, you own it" — khi sửa code thì cũng đồng thời nhận trách nhiệm về chất lượng của nó
- Thực hành quy tắc này giúp ngăn hiệu ứng cửa sổ vỡ (Broken Windows) và tránh tích tụ technical debt
26. Định luật Murphy (Murphy's Law)
Điều gì có thể hỏng thì nhất định sẽ hỏng
- Là cơ sở cho defensive programming, xử lý ngoại lệ và thiết kế sẵn sàng cho sự cố
- Trong phần mềm, cần thiết kế error handling và fallback với tâm thế rằng "lỗi có thể xảy ra thì chắc chắn sẽ xảy ra"
27. Định luật Postel / Nguyên tắc độ bền vững (Postel's Law)
Hãy bảo thủ trong những gì mình làm, và rộng lượng với những gì mình nhận từ người khác
- Khi thiết kế API, nguyên tắc là đầu ra phải tuân thủ chặt chẽ đặc tả, còn đầu vào thì linh hoạt tiếp nhận nhiều định dạng khác nhau
- Nguyên tắc độ bền vững (Robustness Principle) do Jon Postel thiết lập khi thiết kế giao thức TCP/IP
- Là một guideline thực tiễn giúp tăng khả năng tương tác giữa các hệ thống
28. Lý thuyết cửa sổ vỡ (Broken Windows Theory)
Đừng để mặc thiết kế tồi, quyết định sai lầm và code chất lượng thấp
- Chỉ cần một "cửa sổ vỡ" (code tệ) bị bỏ mặc cũng sẽ kéo theo sự suy giảm chất lượng tiếp diễn
- Nếu codebase tích tụ comment TODO, dead code và cảnh báo chưa được xử lý, thì code mới cũng có xu hướng được viết ở mức chất lượng thấp
- Văn hóa sửa cả những vấn đề nhỏ ngay khi phát hiện là rất quan trọng
29. Nợ kỹ thuật (Technical Debt)
Mọi yếu tố làm chậm tốc độ phát triển phần mềm
- Ward Cunningham lần đầu dùng ẩn dụ tài chính này tại OOPSLA năm 1992: chọn đường tắt trong code là vay thời gian từ tương lai
- Gốc nợ (chi phí sửa chữa) + lãi suất (suy giảm năng suất liên tục do code lộn xộn)
- Nợ kỹ thuật có chủ đích đôi khi là hợp lý (thời điểm ra mắt thị trường, prototyping), nhưng kế hoạch hoàn trả là bắt buộc
- Ví dụ điển hình là bỏ qua automated test: bản phát hành có thể thành công, nhưng mỗi lần thay đổi về sau lại phát sinh bug ngoài dự kiến
- Cách giải quyết: refactor, bổ sung test còn thiếu, cải thiện thiết kế
30. Định luật Linus (Linus's Law)
Nếu có đủ nhiều người review thì mọi bug đều dễ dàng bị phát hiện
- Nguyên lý cốt lõi của phát triển mã nguồn mở: nhiều con mắt cùng review code sẽ khiến bug trở thành vấn đề nhỏ
- Eric Raymond đã đặt tên theo Linus Torvalds trong The Cathedral and the Bazaar
- Củng cố tầm quan trọng của văn hóa code review
31. Định luật Kernighan (Kernighan's Law)
Debugging khó gấp đôi so với việc viết code ngay từ đầu
- Vì vậy, nếu bạn viết mã quá thông minh, thì khi gỡ lỗi sẽ không còn đủ thông minh nữa
- Đây là lý do cần viết mã đơn giản, dễ đọc
- Được Brian Kernighan đưa ra trong The Elements of Programming Style
32. Kim tự tháp kiểm thử (Testing Pyramid)
Một dự án nên có nhiều kiểm thử đơn vị nhanh, ít kiểm thử tích hợp hơn, và chỉ một số rất nhỏ kiểm thử UI
- Kiểm thử đơn vị (đáy): nhanh, chi phí thấp, được viết nhiều nhất
- Kiểm thử tích hợp (giữa): xác minh tương tác giữa các thành phần
- Kiểm thử UI/E2E (đỉnh): chậm và dễ vỡ nên cần giảm thiểu
- Mô hình chiến lược kiểm thử do Mike Cohn giới thiệu trong Succeeding with Agile
33. Nghịch lý thuốc trừ sâu (Pesticide Paradox)
Nếu lặp đi lặp lại cùng một bài kiểm thử, hiệu quả của nó sẽ giảm dần theo thời gian
- Vì các lỗi mà bộ kiểm thử hiện có có thể bắt được thì đã bắt hết, nên cần liên tục bổ sung các ca kiểm thử mới
- Việc rà soát và cập nhật bộ kiểm thử định kỳ là điều bắt buộc
34. Các quy luật tiến hóa phần mềm của Lehman (Lehman's Laws of Software Evolution)
Phần mềm phản ánh thế giới thực bắt buộc phải tiến hóa, và sự tiến hóa đó có những giới hạn có thể dự đoán được
- Phần mềm loại E (phản ánh thế giới thực) muốn tiếp tục được sử dụng thì không thể tránh khỏi việc thay đổi liên tục
- Mỗi lần thay đổi, độ phức tạp lại tăng lên; nếu không chủ động quản lý, chất lượng sẽ suy giảm
35. Quy luật Sturgeon (Sturgeon's Law)
90% của mọi thứ đều vô dụng
- Được Theodore Sturgeon đưa ra để đáp lại các phê bình về văn học SF
- Cũng áp dụng cho phần mềm: trong phần lớn mã, công cụ, framework, thứ thực sự xuất sắc chỉ chiếm số ít
- Cần giữ tiêu chuẩn chất lượng cao và tập trung vào 10% có giá trị
Quy mô (Scale)
36. Định luật Amdahl (Amdahl's Law)
Mức tăng tốc nhờ song song hóa bị giới hạn bởi tỷ lệ công việc không thể song song hóa
- Nếu 5% chương trình là tuần tự, thì dù có thêm bao nhiêu bộ xử lý đi nữa, mức tăng tốc tối đa về mặt lý thuyết chỉ là 20 lần
- Nhận thức được giới hạn của song song hóa và giảm các nút thắt tuần tự sẽ hiệu quả hơn
- Được Gene Amdahl đưa ra vào năm 1967
37. Định luật Gustafson (Gustafson's Law)
Có thể đạt được mức tăng tốc đáng kể trong xử lý song song bằng cách tăng kích thước bài toán
- Một góc nhìn bổ sung cho định luật Amdahl: với các bài toán có thể mở rộng thay vì bài toán cố định, việc thêm bộ xử lý là hiệu quả
- Trong xử lý dữ liệu lớn, mô phỏng khoa học... có thể dùng nhiều tài nguyên hơn để giải bài toán lớn hơn
38. Định luật Metcalfe (Metcalfe's Law)
Giá trị của mạng lưới tỷ lệ với bình phương số lượng người dùng
- Nếu có 10 người dùng thì giá trị là 100 đơn vị, còn 100 người dùng thì tăng lên 10.000 đơn vị
- Là nền tảng lý thuyết của hiệu ứng mạng lưới trong mạng xã hội, messenger, marketplace...
- Được Robert Metcalfe đưa ra để giải thích giá trị của công nghệ Ethernet
Thiết kế (Design)
39. Nguyên tắc DRY (Don't Repeat Yourself)
Mọi tri thức chỉ nên có một cách biểu đạt duy nhất, rõ ràng và có thẩm quyền
- Không chỉ là trùng lặp mã, mà còn bao gồm trùng lặp tri thức, logic và dữ liệu
- Trùng lặp khiến phải sửa ở nhiều nơi cùng lúc khi có thay đổi, nên là nguyên nhân gây ra lỗi và sự không nhất quán
- Được Andy Hunt và Dave Thomas định hình trong The Pragmatic Programmer
40. Nguyên tắc KISS (Keep It Simple, Stupid)
Thiết kế và hệ thống nên đơn giản nhất có thể
- Độ phức tạp làm tăng chi phí hiểu, bảo trì và gỡ lỗi
- Giải pháp đơn giản trong đa số trường hợp hiệu quả hơn và cũng ít khả năng phát sinh lỗi hơn
- Bắt nguồn từ nguyên tắc thiết kế do Hải quân Mỹ đưa ra vào thập niên 1960
41. Nguyên tắc SOLID (SOLID Principles)
5 hướng dẫn cốt lõi để cải thiện thiết kế phần mềm
- S — Nguyên tắc trách nhiệm đơn nhất (Single Responsibility): lớp chỉ nên thay đổi vì một lý do duy nhất
- O — Nguyên tắc đóng-mở (Open-Closed): mở cho mở rộng, đóng cho chỉnh sửa
- L — Nguyên tắc thay thế Liskov: kiểu con phải có thể thay thế kiểu cha
- I — Nguyên tắc phân tách interface: client không nên phụ thuộc vào interface mà mình không dùng
- D — Nguyên tắc đảo ngược phụ thuộc: module cấp cao không phụ thuộc vào module cấp thấp mà phụ thuộc vào abstraction
- Được Robert C. Martin hệ thống hóa và Michael Feathers đặt tên viết tắt là SOLID
42. Định luật Demeter (Law of Demeter)
Đối tượng chỉ nên tương tác với các “người bạn” trực tiếp của mình, và nên tránh giao tiếp trực tiếp với các đối tượng xa lạ
- Đây là nguyên tắc cho rằng cần tránh lời gọi chuỗi như
a.getB().getC().doSomething() - Giảm độ kết dính phụ thuộc và tăng cường đóng gói, qua đó thu hẹp phạm vi ảnh hưởng của thay đổi
- Còn được gọi là "nguyên tắc tri thức tối thiểu"
43. Nguyên tắc ít gây ngạc nhiên nhất (Principle of Least Astonishment)
Phần mềm và interface nên hoạt động theo cách ít gây ngạc nhiên nhất cho người dùng và các nhà phát triển khác
- Hàm, API, UI cần có hành vi có thể dự đoán được xét theo tên gọi và convention
- Nếu hàm
delete()thực chất chỉ lưu trữ lại thì sẽ gây ngạc nhiên → lỗi thiết kế - Hành vi thiếu trực quan dẫn đến lỗi và sai sót từ phía người dùng
44. YAGNI (You Aren't Gonna Need It)
Đừng thêm tính năng trước khi thực sự cần đến nó
- Là nguyên tắc cốt lõi của Extreme Programming (XP), do Ron Jeffries đưa ra vào cuối thập niên 1990
- Nếu viết mã chỉ vì "có thể sau này sẽ cần", sẽ dẫn tới thiết kế quá mức và gánh nặng bảo trì
- Để thực hành YAGNI, cần có sự tự tin vào việc refactoring (độ phủ kiểm thử tốt, CI)
- Nếu hiện tại chỉ cần xuất JSON thì chỉ triển khai JSON, còn XML/YAML... sẽ bổ sung khi có yêu cầu
Ra quyết định (Decisions)
45. Hiệu ứng Dunning-Kruger (Dunning-Kruger Effect)
Bạn càng biết ít về một thứ thì càng có xu hướng tự tin hơn về nó
- Hiện tượng lập trình viên mới đánh giá thấp độ khó của hệ thống phức tạp, trong khi chuyên gia lại khiêm tốn hơn với tri thức của chính mình
- Điều quan trọng là nâng cao độ chính xác trong tự nhận thức thông qua review code, mentoring và học tập liên tục
46. Dao cạo Hanlon (Hanlon's Razor)
Đừng quy cho ác ý những gì có thể được giải thích đầy đủ bằng sự ngu ngốc hoặc bất cẩn
- Trước khi diễn giải mã kém hay quyết định sai của đồng nghiệp là hành vi cản trở có chủ ý, hãy cân nhắc trước sự thiếu hiểu biết, sai sót, hoặc thiếu thời gian
- Là nền tảng cho lòng tin trong nhóm và giao tiếp mang tính xây dựng
47. Dao cạo Occam (Occam's Razor)
Cách giải thích đơn giản nhất thường là cách chính xác nhất
- Khi gỡ lỗi, hãy kiểm tra khả năng đơn giản nhất trước thay vì lao ngay vào các nguyên nhân phức tạp
- Trong thiết kế kiến trúc cũng vậy, nên ưu tiên tìm giải pháp đơn giản trước khi thêm các lớp abstraction không cần thiết
48. Ngụy biện chi phí chìm (Sunk Cost Fallacy)
Hiện tượng tiếp tục duy trì một lựa chọn gây bất lợi chỉ vì đã đầu tư thời gian hoặc năng lượng vào nó
- Dù một tính năng phát triển suốt 6 tháng đi sai hướng, ta vẫn không nỡ bỏ vì thời gian đã đầu tư
- Quyết định đúng đắn phải dựa trên giá trị tương lai, không phải khoản đầu tư trong quá khứ
49. Bản đồ không phải là lãnh thổ (The Map Is Not the Territory)
Biểu diễn về thực tại (mô hình) không đồng nhất với chính thực tại
- UML diagram, tài liệu kiến trúc, data model... chỉ là xấp xỉ của thực tế
- Không nên tuyệt đối hóa mô hình; cần quan sát cách hệ thống thực sự vận hành và cập nhật mô hình theo đó
50. Thiên kiến xác nhận (Confirmation Bias)
Xu hướng ưu tiên những thông tin ủng hộ niềm tin hoặc ý tưởng sẵn có
- Cái bẫy của việc chỉ chọn lọc các thông tin có lợi cho tech stack hoặc quyết định thiết kế mà bản thân đã chọn
- Chủ động tìm kiếm bằng chứng phản bác và chấp nhận nhiều góc nhìn khác nhau là cốt lõi của việc ra quyết định cân bằng
51. Hype Cycle và định luật Amara (The Hype Cycle & Amara's Law)
Con người có xu hướng đánh giá quá cao tác động ngắn hạn của công nghệ, và đánh giá quá thấp ảnh hưởng dài hạn của nó
- Hype Cycle của Gartner: kích hoạt công nghệ → đỉnh cao của kỳ vọng thổi phồng → vực sâu vỡ mộng → sườn dốc khai sáng → giai đoạn ổn định năng suất
- Khi áp dụng công nghệ mới (blockchain, AI, v.v.), cần tránh bị cuốn theo cơn sốt ngắn hạn mà phải đánh giá tính thực dụng dài hạn
52. Hiệu ứng Lindy (The Lindy Effect)
Thứ gì đã được sử dụng lâu thì càng có khả năng tiếp tục được sử dụng trong tương lai
- Những công nghệ đã được dùng hàng chục năm như UNIX, SQL, ngôn ngữ C có khả năng cao sẽ còn tồn tại lâu dài
- Cơ sở lý thuyết khi chọn công nghệ đã được kiểm chứng thay vì framework mới
- Được Nassim Nicholas Taleb phổ biến rộng rãi trong Antifragile
53. Tư duy nguyên lý đầu tiên (First Principles Thinking)
Phương pháp tư duy phân rã một vấn đề phức tạp thành các thành phần cơ bản nhất rồi tái cấu trúc từ đó
- Loại bỏ các thông lệ và giả định sẵn có, xuất phát từ sự thật cốt lõi để tìm ra lời giải
- Nổi tiếng với ví dụ Elon Musk áp dụng để cắt giảm chi phí tên lửa tại SpaceX
- Khi thiết kế hệ thống phức tạp, cần cảnh giác với lối nghĩ kiểu "vốn dĩ người ta vẫn làm vậy"
54. Tư duy đảo ngược (Inversion)
Phương pháp giải quyết vấn đề bằng cách giả định kết quả ngược lại và suy luận theo chiều ngược
- Thay vì nghĩ "làm sao để thành công", hãy nghĩ trước "làm sao để thất bại" để nhận diện yếu tố rủi ro
- Là cơ sở lý thuyết cho phân tích chế độ lỗi (Failure Mode Analysis) và pre-mortem
- Mô hình tư duy thường được Charlie Munger sử dụng
55. Nguyên lý Pareto / quy luật 80/20 (Pareto Principle)
80% vấn đề phát sinh từ 20% nguyên nhân
- 80% tổng số bug có xu hướng tập trung trong 20% mã nguồn
- Tập trung nguồn lực vào 20% có ảnh hưởng lớn nhất là chiến lược phân bổ tài nguyên hiệu quả
- Bắt nguồn từ nguyên lý Vilfredo Pareto quan sát được từ phân bố sở hữu đất đai ở Ý
56. Định luật Cunningham (Cunningham's Law)
Cách tốt nhất để nhận được câu trả lời đúng trên Internet không phải là đặt câu hỏi, mà là đăng một câu trả lời sai
- Mọi người thường tích cực sửa thông tin sai hơn là trả lời câu hỏi
- Là quy luật mang tên Ward Cunningham (người phát minh Wiki), nhưng thực tế cái tên này do Steven McGeady đặt ra
- Một góc nhìn có thể áp dụng cho việc viết tài liệu hoặc chia sẻ tri thức trong cộng đồng mã nguồn mở
2 bình luận
Ý kiến trên Hacker News
Tôi đặc biệt ghét câu "tối ưu hóa sớm là cội nguồn của mọi điều xấu". Câu này xuất hiện trong bối cảnh năm 1974 nên tiền đề đã khác hiện nay. Khi đó tối ưu hóa gần với assembly và tính từng chu kỳ CPU, còn bây giờ hiệu năng chủ yếu là vấn đề của lựa chọn kiến trúc, nên phải được cân nhắc ngay từ đầu. Lời khuyên dùng profiling để bắt các lỗi hiệu năng kiểu O(n²) phát sinh ngoài ý muốn vẫn còn đúng, nhưng khi chi phí của lớp trừu tượng đã trở thành điểm nghẽn thì người ta thường chỉ chắp thêm cache và song song hóa, khiến hệ thống vừa phức tạp hơn vừa chậm hơn. Giờ tôi thấy tối ưu hóa muộn cũng tệ ngang, thậm chí có khi còn tệ hơn tối ưu hóa sớm
Tôi tiếc là thiếu Curly's Law. Một biến chỉ nên mang một ý nghĩa duy nhất, không nên chứa giá trị của các miền khác nhau tùy ngữ cảnh hay đồng thời làm hai vai trò. Phép so sánh kiểu "vừa là chất đánh bóng sàn vừa là topping món tráng miệng" rất đúng với trường hợp này
Tôi cảm thấy khi gom các "định luật" phần mềm này lại với nhau thì chúng có quá nhiều mâu thuẫn nội tại, đến mức cuối cùng rất dễ chọn đúng câu nào hợp để biện minh cho lập luận của mình. Điều thực sự khó là biết khi nào nên phá vỡ định luật nào, và vì sao phải phá vỡ nó
Tôi muốn đùa rằng trong phiên bản định luật kỹ nghệ phần mềm năm 2026, mọi website sẽ được vibe code bằng Claude Opus. Kết quả là màu nền sẽ thành tông kem giống Anthropic, font và độ đậm sẽ bị trộn quá đà như thể một người mới nhập môn thiết kế vừa học typography xong, UI dạng card thì tràn ngập, và sẽ lặp đi lặp lại các mẫu như viền màu bo tròn chỉ ở một phía của card
Tôi nghĩ Boyd's Law of Iteration cũng nên có mặt. Ý của nó là khi xử lý sự phức tạp, lặp nhanh nhiều khi đem lại kết quả tốt hơn phân tích sâu, và nếu nhớ rằng Boyd là người tạo ra OODA loop thì điều này lại càng ấn tượng
Tôi nghĩ trong số các bình luận đã bị xóa có một siêu định luật hay nhất cho bài này. Nội dung là: "mọi định luật kỹ nghệ phần mềm đều ngay lập tức bị hiểu sai và bị áp dụng một cách vô phê phán theo cách khiến chính tác giả gốc phải kinh hãi"; nhìn vào cách LLM hành xử khi thiếu ngữ cảnh cốt lõi thì tôi lại càng hiểu vì sao. Cuối cùng, việc nén hàng chục năm khôn ngoan và kinh nghiệm thành một câu danh ngôn vốn có giới hạn
Tôi muốn hỏi rằng nếu đã định vibe code cả một "trang danh sách các định luật kỹ nghệ phần mềm", thì việc không làm luôn một trang Wikipedia là vi phạm định luật nào
Tôi nghĩ những điều như thế này nên là kiến thức phổ thông cơ bản đến mức trở thành yêu cầu tuyển dụng. Nó giống kiểu câu chuyện mà ai cũng nên biết
Dù không phải định luật dành riêng cho phần mềm, tôi thường dạy Chesterton's Fence cho intern và người mới vào làm đầu tiên
Tôi cảm thấy định luật bảo toàn độ phức tạp của Tesler tự thân câu chữ đã cho thấy ngay một trực giác hay. Mỗi ứng dụng đều có một mức độ phức tạp cố hữu không thể loại bỏ, chỉ có thể di chuyển đi chỗ khác. Nhưng khi đi vào phần giải thích thì nó dường như lại thu hẹp thành lời khuyên khá bình thường là hãy làm người dùng bớt khổ hơn. Người dùng rốt cuộc vẫn phải mang một mức độ phức tạp cần thiết, và nếu giảm bừa bãi thì sản phẩm rất dễ trở thành món đồ chơi thiếu linh hoạt. Vì vậy khi refactor, tôi thấy hữu ích hơn nếu ghi nhớ rằng đơn giản hóa một phần có thể khiến phần khác trở nên phức tạp hơn
Lúc làm
vibe codingthì trước mắt thấy ổn, nhưng cuối cùng có vẻ vẫn phải trả giá...